用Abp实现双因素认证(Two-Factor Authentication, 2FA)登录(一):认证模块
@
在之前的博文 用Abp实现短信验证码免密登录(一):短信校验模块 一文中,我们实现了用户验证码校验模块,今天来拓展这个模块,使Abp用户系统支持双因素认证(Two-Factor Authentication)功能。
双因素认证(Two-Factor Authentication,简称 2FA)是使用两个或多个因素的任意组合来验证用户身份,例如用户提供密码后,还要提供短消息发送的验证码,以证明用户确实拥有该手机。
国内大多数网站在登录屏正常登录后,检查是否有必要进行二次验证,如果有必要则进入二阶段验证屏,如下图:


接下来就来实践这个小项目
本示例基于之前的博文内容,你需要登录并绑定正确的手机号,才能使用双因素认证。示例代码已经放在了GitHub上:Github:matoapp-samples
原理
查看Abp源码,Abp帮我们定义了几个Setting,用于配置双因素认证的相关功能。确保在数据库中将Abp.Zero.UserManagement.TwoFactorLogin.IsEnabled打开。
public static class TwoFactorLogin
{
/// <summary>
/// "Abp.Zero.UserManagement.TwoFactorLogin.IsEnabled".
/// </summary>
public const string IsEnabled = "Abp.Zero.UserManagement.TwoFactorLogin.IsEnabled";
/// <summary>
/// "Abp.Zero.UserManagement.TwoFactorLogin.IsEmailProviderEnabled".
/// </summary>
public const string IsEmailProviderEnabled = "Abp.Zero.UserManagement.TwoFactorLogin.IsEmailProviderEnabled";
/// <summary>
/// "Abp.Zero.UserManagement.TwoFactorLogin.IsSmsProviderEnabled".
/// </summary>
public const string IsSmsProviderEnabled = "Abp.Zero.UserManagement.TwoFactorLogin.IsSmsProviderEnabled";
...
}
在AbpUserManager的GetValidTwoFactorProvidersAsync方法中
Abp.Zero.UserManagement.TwoFactorLogin.IsSmsProviderEnabled开启后将添加“Phone”到Provider中,将启用短信验证方式。
Abp.Zero.UserManagement.TwoFactorLogin.IsEmailProviderEnabled开启后将添加“Email”到Provider中,将启用邮箱验证方式。
var isEmailProviderEnabled = await IsTrueAsync(
AbpZeroSettingNames.UserManagement.TwoFactorLogin.IsEmailProviderEnabled,
user.TenantId
);
if (provider == "Email" && !isEmailProviderEnabled)
{
continue;
}
var isSmsProviderEnabled = await IsTrueAsync(
AbpZeroSettingNames.UserManagement.TwoFactorLogin.IsSmsProviderEnabled,
user.TenantId
);
if (provider == "Phone" && !isSmsProviderEnabled)
{
continue;
}
在迁移中添加双因素认证的配置项
//双因素认证
AddSettingIfNotExists(AbpZeroSettingNames.UserManagement.TwoFactorLogin.IsEnabled, "true", tenantId);
AddSettingIfNotExists(AbpZeroSettingNames.UserManagement.TwoFactorLogin.IsSmsProviderEnabled, "true", tenantId);
AddSettingIfNotExists(AbpZeroSettingNames.UserManagement.TwoFactorLogin.IsEmailProviderEnabled, "true", tenantId);
将默认User的IsTwoFactorEnabled字段设为true
public User()
{
this.IsTwoFactorEnabled= true;
}
用户验证码校验模块
使用AbpBoilerplate.Sms作为短信服务库。
之前定义了DomainService接口,已经实现了验证码的发送、验证码校验、解绑手机号、绑定手机号
这4个功能,通过定义用途(purpose)字段以校验区分短信模板
public interface ICaptchaManager
{
Task BindAsync(string token);
Task UnbindAsync(string token);
Task SendCaptchaAsync(long userId, string phoneNumber, string purpose);
Task<bool> VerifyCaptchaAsync(string token, string purpose = "IDENTITY_VERIFICATION");
}
添加一个用于双因素认证的purpose,在CaptchaPurpose枚举类型中添加TWO_FACTOR_AUTHORIZATION
public const string TWO_FACTOR_AUTHORIZATION = "TWO_FACTOR_AUTHORIZATION";
在SMS服务商管理端后台申请一个短信模板,用于双因素认证。

打开短信验证码的领域服务类SmsCaptchaManager, 添加TWO_FACTOR_AUTHORIZATION对应短信模板的编号
public async Task SendCaptchaAsync(long userId, string phoneNumber, string purpose)
{
var captcha = CommonHelper.GetRandomCaptchaNumber();
var model = new SendSmsRequest();
model.PhoneNumbers = new string[] { phoneNumber };
model.SignName = "MatoApp";
model.TemplateCode = purpose switch
{
CaptchaPurpose.BIND_PHONENUMBER => "SMS_255330989",
CaptchaPurpose.UNBIND_PHONENUMBER => "SMS_255330923",
CaptchaPurpose.LOGIN => "SMS_255330901",
CaptchaPurpose.IDENTITY_VERIFICATION => "SMS_255330974"
CaptchaPurpose.TWO_FACTOR_AUTHORIZATION => "SMS_1587660" //添加双因素认证对应短信模板的编号
};
...
}
双因素认证模块
创建双因素认证领域服务类TwoFactorAuthorizationManager。
创建方法IsTwoFactorAuthRequiredAsync,返回登录用户是否需要双因素认证,若未开启TwoFactorLogin.IsEnabled、用户未开启双因素认证,或没有添加验证提供者,则跳过双因素认证。
public async Task<bool> IsTwoFactorAuthRequiredAsync(AbpLoginResult<Tenant, User> loginResult)
{
if (!await settingManager.GetSettingValueAsync<bool>(AbpZeroSettingNames.UserManagement.TwoFactorLogin.IsEnabled))
{
return false;
}
if (!loginResult.User.IsTwoFactorEnabled)
{
return false;
}
if ((await _userManager.GetValidTwoFactorProvidersAsync(loginResult.User)).Count <= 0)
{
return false;
}
return true;
}
创建TwoFactorAuthenticateAsync,此方法根据回传的provider和token值校验用户是否通过双因素认证。
public async Task TwoFactorAuthenticateAsync(User user, string token, string provider)
{
if (provider == "Email")
{
var isValidate = await emailCaptchaManager.VerifyCaptchaAsync(token, CaptchaPurpose.TWO_FACTOR_AUTHORIZATION);
if (!isValidate)
{
throw new UserFriendlyException("验证码错误");
}
}
else if (provider == "Phone")
{
var isValidate = await smsCaptchaManager.VerifyCaptchaAsync(token, CaptchaPurpose.TWO_FACTOR_AUTHORIZATION);
if (!isValidate)
{
throw new UserFriendlyException("验证码错误");
}
}
else
{
throw new UserFriendlyException("验证码提供者错误");
}
}
创建SendCaptchaAsync,此方用于发送验证码。
public async Task SendCaptchaAsync(long userId, string Provider)
{
var user = await _userManager.FindByIdAsync(userId.ToString());
if (user == null)
{
throw new UserFriendlyException("找不到用户");
}
if (Provider == "Email")
{
if (!user.IsEmailConfirmed)
{
throw new UserFriendlyException("未绑定邮箱");
}
await emailCaptchaManager.SendCaptchaAsync(user.Id, user.EmailAddress, CaptchaPurpose.TWO_FACTOR_AUTHORIZATION);
}
else if (Provider == "Phone")
{
if (!user.IsPhoneNumberConfirmed)
{
throw new UserFriendlyException("未绑定手机号");
}
await smsCaptchaManager.SendCaptchaAsync(user.Id, user.PhoneNumber, CaptchaPurpose.TWO_FACTOR_AUTHORIZATION);
}
else
{
throw new UserFriendlyException("验证提供者错误");
}
}
改写登录
接下来将双因素认证逻辑添加到登录流程中。
在web.core项目中,
添加类SendTwoFactorAuthenticateCaptchaModel,发送验证码时将一阶段返回的userId和选择验证方式的provider传入
public class SendTwoFactorAuthenticateCaptchaModel
{
[Range(1, long.MaxValue)]
public long UserId { get; set; }
[Required]
public string Provider { get; set; }
}
将验证码Token,和验证码提供者Provider的定义添加到AuthenticateModel中
public string TwoFactorAuthenticationToken { get; set; }
public string TwoFactorAuthenticationProvider { get; set; }
将提供者列表TwoFactorAuthenticationProviders,和是否需要双因素认证RequiresTwoFactorAuthenticate的定义添加到AuthenticateResultModel中
public bool RequiresTwoFactorAuthenticate { get; set; }
public IList<string> TwoFactorAuthenticationProviders { get; set; }
打开TokenAuthController,注入UserManager和TwoFactorAuthorizationManager服务对象
添加终节点SendTwoFactorAuthenticateCaptcha,用于前端调用发送验证码
[HttpPost]
public async Task SendTwoFactorAuthenticateCaptcha([FromBody] SendTwoFactorAuthenticateCaptchaModel model)
{
await twoFactorAuthorizationManager.SendCaptchaAsync(model.UserId, model.Provider);
}
改写Authenticate方法如下:
[HttpPost]
public async Task<AuthenticateResultModel> Authenticate([FromBody] AuthenticateModel model)
{
//用户名密码校验
var loginResult = await GetLoginResultAsync(
model.UserNameOrEmailAddress,
model.Password,
GetTenancyNameOrNull()
);
await userManager.InitializeOptionsAsync(loginResult.Tenant?.Id);
//判断是否需要双因素认证
if (await twoFactorAuthorizationManager.IsTwoFactorAuthRequiredAsync(loginResult))
{
//判断是否一阶段
if (string.IsNullOrEmpty(model.TwoFactorAuthenticationToken))
{
//一阶登录完成,返回结果,等待二阶段登录
return new AuthenticateResultModel
{
RequiresTwoFactorAuthenticate = true,
UserId = loginResult.User.Id,
TwoFactorAuthenticationProviders = await userManager.GetValidTwoFactorProvidersAsync(loginResult.User),
};
}
//二阶段,双因素认证校验
else
{
await twoFactorAuthorizationManager.TwoFactorAuthenticateAsync(loginResult.User, model.TwoFactorAuthenticationToken, model.TwoFactorAuthenticationProvider);
}
}
//二阶段完成,返回最终登录结果
var accessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity));
return new AuthenticateResultModel
{
AccessToken = accessToken,
EncryptedAccessToken = GetEncryptedAccessToken(accessToken),
ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds,
UserId = loginResult.User.Id,
};
}

至此,双因素认证的后端逻辑已经完成,接下来我们将补充“记住”功能,实现一段时间内免验证。
用Abp实现双因素认证(Two-Factor Authentication, 2FA)登录(一):认证模块的更多相关文章
- Apache shiro集群实现 (三)shiro身份认证(Shiro Authentication)
Apache shiro集群实现 (一) shiro入门介绍 Apache shiro集群实现 (二) shiro 的INI配置 Apache shiro集群实现 (三)shiro身份认证(Shiro ...
- Authentication(Spring Security 认证笔记)
这篇文章是对Spring Security的Authentication模块进行一个初步的概念了解,知道它是如何进行用户认证的 考虑一个大家比较熟悉的标准认证过程: 1.用户使用username和pa ...
- 业余草双因素认证(2FA)教程
所谓认证(authentication)就是确认用户的身份,是网站登录必不可少的步骤.密码是最常见的认证方法,但是不安全,容易泄露和冒充.越来越多的地方,要求启用双因素认证(Two-factor au ...
- 双因素认证(2FA)教程
所谓认证(authentication)就是确认用户的身份,是网站登录必不可少的步骤. 密码是最常见的认证方法,但是不安全,容易泄露和冒充. 越来越多的地方,要求启用 双因素认证(Two-factor ...
- Linux 利用Google Authenticator实现ssh登录双因素认证
1.介绍 双因素认证:双因素身份认证就是通过你所知道再加上你所能拥有的这二个要素组合到一起才能发挥作用的身份认证系统.双因素认证是一种采用时间同步技术的系统,采用了基于时间.事件和密钥三变量而产生的一 ...
- Linux 之 利用Google Authenticator实现用户双因素认证
一.介绍:什么是双因素认证 双因素身份认证就是通过你所知道再加上你所能拥有的这二个要素组合到一起才能发挥作用的身份认证系统.双因素认证是一种采用时间同步技术的系统,采用了基于时间.事件和密钥三变量而产 ...
- 轻松搭建CAS 5.x系列(8)-在CAS Server增加双因素认证(DUO版)
概述说明 为了让系统更加安全,很多登录会加入双因素认证.何为双因素,如果把登陆作为开一扇门的话,那就是在原来的锁上再加一把锁,第二锁用新的钥匙,这样安全系数就更加高了. CAS是通过账号名和密码来认证 ...
- 操作系统(AIX)双因素身份认证解决方案-中科恒伦CKEY DAS
一.场景分析 操作系统是管理计算机硬件与软件资源的计算机程序,用于工作中的进程管理.存储管理.设备管理.文件管理.作业管理等,十分重要,安全等级极高! 二.问题分析 1.密码设置简单,非常容易被撞 ...
- ASP.NET Core & 双因素验证2FA 实战经验分享
必读 本文源码核心逻辑使用AspNetCore.Totp,为什么不使用AspNetCore.Totp而是使用源码封装后面将会说明. 为了防止不提供原网址的转载,特在这里加上原文链接: https:// ...
- ABP源码分析四十二:ZERO的身份认证
ABP Zero模块通过自定义实现Asp.Net Identity完成身份认证功能, 对Asp.Net Identity做了较大幅度的扩展.同时重写了ABP核心模块中的permission功能,以实现 ...
随机推荐
- Arrays.asList()的使用
JDK文档说明: @SafeVarargs public static <T> List<T> asList(T... a) 返回由指定数组支持的固定大小的列表(将返回的列表更 ...
- Latex Algorithm 语法错误导致无法编译
遇到了几种情况: 1. for 循环没加{} 2. $\textbf{T_i}$, 想要加粗T,但是把i也扩进去了,latex就不懂了,于是一直recompile不出来说超时什么什么的,把i放到外面就 ...
- Spring系列之类型转换-12
目录 类型转换 转换器 SPI 使用`ConverterFactory` 使用`GenericConverter` `ConversionService` 配置一个`ConversionService ...
- PLC入门笔记11
1.开关? 输入 拨杆开关.点动开关.常开.常闭开关 霍尔接近开关(磁场 N极导通 3线+-DC24V ).电容接近开关(非金属).电感接近开关(金属) 2.输入接线? NPN型,不需要外接电源,直接 ...
- FCC 高级算法题 验证电话号码
Validate US Telephone Numbers 如果传入字符串是一个有效的美国电话号码,则返回 true. 用户可以在表单中填入一个任意有效美国电话号码. 下面是一些有效号码的例子(还有下 ...
- KMP算法学习记录
Foreword: 初学KMP匹配算法,不得其门,总感觉自己想,想不出来,看书上文字解释晦涩难懂.不能准确的捕捉算法设计时候的灵光和思路 .于是自己试着完成了一遍,现将过程记录下来,以供复习. Con ...
- 优先使用C++的别名声明(using)来替换typedef
C++98中,我们如果想用简写的方式表达一个类型,那么可以使用typedef关键字: typedef std::unique_ptr<std::unordered_map<std::str ...
- (c语言描述 函数递归汉诺塔)
汉诺塔:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具.大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘.大梵天命令婆罗门把圆盘从下面开始按大小顺序 ...
- 持续集成环境(5)-Maven安装和配置
在Jenkins集成服务器上,我们需要安装Maven来编译和打包项目. 安装Maven 1.下载Maven软件到jenkins服务器上 wget https://mirrors.aliyun.com/ ...
- centos 添加yum源失败,ping 百度没响应
1. curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.r ...