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. 如何将java私有库(jar)提交至公服/公共仓库(central repository)-手动版

    如何将java私有库(jar)提交至公服/公共仓库(central repository)-手动版 准备 GunPG(用于asc签名) 项目代码(建议是maven结构的) sonatype账号(htt ...

  2. docker安装配置redis

    ​ 安装redis docker pull redis 配置数据路径 mkdir -p /home/redis/data docker启动 docker run -d -v /home/redis/d ...

  3. 【Amadeus原创】修改docker里面网站的port端口映射

    切记:以下顺序千万不能颠倒!否则不生效! 1. 查看需要修改的容器,记住container id docker ps -a 2. 停止容器 docker stop xxx 3. 修改容器的端口映射配置 ...

  4. 零售经营“新赛道” ——基于手机银行APP专区调研的客群精细化运营分析报告

    ​ 随着银行业竞争的不断深入及新客户增量日渐"到顶",各家银行的客群竞争逐渐由"跑马圈地"进入"精耕细作"的新阶段,在客群精准化服务方面不断 ...

  5. 中电金信技术实践|Redis哨兵原理及安装部署分享

    ​ 导语:本文主要围绕redis Sentinel的基本概念.部署Redis Sentinel模式和其相关的API等内容进行介绍,并讲述哨兵与主从关系的区别,以及哨兵机制是怎么实现高可用的,希望可以与 ...

  6. 【Python】【爬虫】【问题解决方案记录】调试输出存在数据,print在控制台确丢失数据

    如下图,调试可以看到数据是完整的 但是print输出的,恰好丢失了中间的一大堆数据.对,下图打问号的地方应该是小说才对. 看代码可能看不出缺失内容,可视化看看 对吧,刚好缺失了小说. 后来我尝试用写文 ...

  7. 【Python】【Flask】【字符串索引】计算人民币与美元的相互计算

    目录 简介 Python Code 导包 设置首页 计算的接口 问题0:设置请求方式 问题1:关于接收数据可能存在的问题 问题2:返回结果 启动 完整代码 HTML Code 问题分析 分析:获取下拉 ...

  8. Qt音视频开发6-ffmpeg解码处理

    一.前言 采用ffmpeg解码,是所有视频监控开发人员必备的技能,绕不过去的一个玩意,甚至可以说是所有音视频开发人员的必备技能.FFmpeg是一套可以用来记录.转换数字音频.视频,并能将其转化为流的开 ...

  9. asp.net mvc中换肤机制类库 ThemedViewEngines

    制作blog系统或者通用cms系统的时候,我们经常会用到Theme功能.asp.net mvc中的一种实现方式,是继承实现RazorViewEngine即可. 这是在GitHub中找到的一个示例:ht ...

  10. Linux部署Redis哨兵集群 一主两从三哨兵

    目录一.哨兵集群架构介绍二.下载安装Redis2.1.选择需要安装的Redis版本2.2.下载并解压Redis2.3.编译安装Redis三.搭建Redis一主两从集群3.1.准备配置文件3.1.1.准 ...