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. 【SpringMVC】获取请求参数的方式

    SpringMVC获取请求参数的方式 目录 SpringMVC获取请求参数的方式 方式1:ServletAPI 方法2:通过控制器方法的形参获取请求参数 方法3:@RequestParam 方法4:@ ...

  2. shell学习之路——取参数

    shell中参数的调用方式: 1.$0-9:表示第0个到第9个参数,其中$0表示文件执行路径.如:$0,$1. 2.${10以后}:如果参数数目大于9个,可以用${10},${11}...等方式表示. ...

  3. java 随机生成字符串 RandomStringUtils

    使用RandomStringUtils,可以选择生成随机字符串,可以是全字母.全数字或自定义生成字符等等... 其最基础的方法如下: public static String random(int c ...

  4. 龙哥量化:通达信常用指标写法macd数值太小怎么办macd的数值是0.01怎么放大

    1.先放公式 MACD 放大坐标系 1000倍 参数 12.26.9DIF:EMA(CLOSE*1000,12)-EMA(CLOSE*1000,26);DEA:EMA(DIF,9);MACD:(DIF ...

  5. QtCreator中pro项目文件格式说明

    名称 说明 QT += core gui 添加本项目中需要的模块,影响后面代码文件include的时候自动弹出下拉选择,如果pro文件没有引入该模块则无法自动语法提示,一般打包发布的时候对应动态库文件 ...

  6. Qt编写安防视频监控系统46-视频存储

    一.前言 在整个视频监控系统的开发迭代升级过程中,遇到过各种奇奇怪怪的需求,都是客户提出来的,有些需求很合理,有些就不那么的自然了,牢记这客户是上帝的原则,能满足的尽量满足.相信各位同行的研发人员都会 ...

  7. windows11使用pycharm连接wsl2开发基于poetry的python项目

    windows11使用pycharm连接wsl2开发基于poetry的python项目 背景:公司开发的python项目用到了某个只提供了Linux版本的包,遂研究了一番如何在windows环境下进行 ...

  8. (三).NET6.0使用Autofac实现依赖注入

    1.添加依赖注入的两个关键包 Autofac.Extensions.DependencyInjection 和 Autofac.Extras.DynamicProxy 2.在Program中添加Aut ...

  9. CDS标准视图:付款锁定原因 I_PaymentBlockingReason

    视图名称:付款锁定原因 I_PaymentBlockingReason 视图类型:基础视图 视图代码: 点击查看代码 //Documentation about annotations can be ...

  10. x86平台SIMD编程入门(2):通用指令

    1.重解释转换 虽然128位的XMM寄存器在硬件上只是256位YMM寄存器的下半部分,但在C++中它们是不同的类型.有一些intrinsic函数可以将它们重新解释为不同的类型,如下表所示,行代表源类型 ...