用Abp实现两步验证(Two-Factor Authentication,2FA)登录(一):认证模块

article/2025/10/6 14:37:03

文章目录

    • 原理
    • 用户验证码校验模块
    • 双因素认证模块
    • 改写登录
    • 项目地址

在之前的博文 用Abp实现短信验证码免密登录(一):短信校验模块 一文中,我们实现了用户验证码校验模块,今天来拓展这个模块,使Abp用户系统支持两步验证功能。

两步验证,又称双重验证或双因素认证(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,};
}

在这里插入图片描述

至此,双因素认证的后端逻辑已经完成。

项目地址

Github:matoapp-samples


http://chatgpt.dhexx.cn/article/BLOuMQ8b.shtml

相关文章

用Abp实现两步验证(Two-Factor Authentication,2FA)登录(二):Vue网页端开发

文章目录 发送验证码登录退出登录界面控件获取用户信息功能项目地址 前端代码的框架采用vue.js elementUI 这套较为简单的方式实现&#xff0c;以及typescript语法更方便阅读。 首先添加全局对象&#xff1a; loginForm: 登录表单对象 twoFactorData: 两步验证数据&#xff0…

快速接入Google两步认证Google Authenticator

(一)介绍 既然来看该文章就应该知道Google的两步认证是干什么的&#xff0c;这边再提供一次app的下载链接 (apkpure搜索谷歌身份验证器) 验证原理讲解&#xff1a; 在数据库中查找该登陆用户之前绑定的32位随机码&#xff08;该码一般会存入数据库&#xff09;调用API传入32位…

两步验证 非双重认证

Two-factor authentication must be turned on for your Apple ID. After you turn it on, signing into your developer account will require both your password and access to your trusted devices or trusted phone number. 今天Xcode 真机调试, 突然不正常了. 本着热爱…

google账号异步新设备登录需要两次两步验证问题

备注&#xff1a;华为手机&#xff0c;在谷歌三件套插件已经下载的情况下&#xff0c;还是无法收到数字点击验证 先说下现象&#xff0c;比如你的谷歌账号从别人那购买的&#xff0c;然后辅助电话与邮箱已经全部替换成了自己的信息&#xff0c;一般为了安全我们会开启两步验证&…

谷歌两步验证器身份怎么开Authenticator安卓app下载安装方法教程

国内互联网公司一般采取手机收验证码的方式对账号进行身份验证&#xff0c;增强账号的安全性。但是在国外通常采取使用谷歌两步身份验证器 (Google Authenticator&#xff09;&#xff0c;谷歌谷歌两步身份验证器的方便之处主要体现在&#xff1a; 1.在无网络的情况下也可以使用…

如何开发两步验证功能

什么是两步验证 两步验证&#xff0c;是指用户登录账户的时候&#xff0c;除了要输入用户名和密码&#xff0c;还要求用户输入一个动态密码&#xff0c;为帐户添加了一层额外保护。这个动态密码要么是专门的硬件&#xff0c;要么由用户手机APP提供。即使入侵者窃取了用户密码&a…

兩步验证的原理

被盗号 “您的账号密码有误,请重新输入” 小卢盯着电脑屏幕看了5分钟,心里纳闷,昨天还能登录,怎么今天就密码错误了,难不成我被盗号了?想到这里,小卢赶紧给自己的程序员好友小王打电话。 小卢:“小王,我在XX网站的账号被盗了!” 小王:“确定被盗了?赶紧把密码找…

(01)Webrtc::Fec与Nack的二三事

写在前面&#xff1a;要理解Fec与Nack逻辑&#xff0c;我喜欢先从接受端看&#xff0c; 理解了Fec与Nack是如何被使用的&#xff0c;才能更好的明白不同的机制应该怎么用&#xff0c;在什么场合用。 更新丢包逻辑 void PacketBuffer::UpdateMissingPackets(uint16_t seq_num)…

Channel closed; cannot ack/nack

再一次用rabbmitmq的时候遇到了 Channel closed&#xff1b; cannot ack/nack的异常信息&#xff0c;这个可能是因为rabbmitmq默认的模式是自动ack&#xff0c;我没有配置手动ack 然后在代码里又basicack了。 MessageProperties properties message.getMessageProperties();l…

简述WebRTC中的丢包重传Nack的实现

一 简述 接收端发现序列号不连续&#xff0c;发送RTCP FB Nack包&#xff0c;发送端从历史队列中查找该包&#xff0c;再发送RTP包&#xff0c;但WebRTC用的RTX重发该包&#xff0c;ssrc和原视频流不同&#xff0c;pt也不同。 artpmap:96 H264/90000 artcp-fb:96 goog-remb a…

WEBRTC浅析(五)视频Nack包的发送判断逻辑以及数据流

这篇文章是对webrtc 中Nack包发送机制的梳理&#xff0c;主要包括三个部分&#xff1a; 第一部分&#xff0c;介绍RTCP包中&#xff0c;Nack包的规范。 第二部分&#xff0c;介绍在WEBRTC中&#xff0c;Nack发送机制的数据流程图。 第三部分&#xff0c;介绍在WEBRTC中&#xf…

RabbitMQ的ack和nack机制

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、ACK机制二、主动ACK三、手动ACK四、Nack机制五、MQ unack的影响总结 前言 本文主要讨论RabbitMQ消费者的ack和nack机制&#xff0c;并且关注ack和nack使用…

RTCP 协议的 NACK 报文

接收方定时把所有未收到的包序号通过反馈报文通知到发送方进行重传。 相对 ARQ带来的改进&#xff1a;减少的反馈包的频率和带宽占用&#xff0c;同时也能比较及时地通知发送方进行丢包重传。 NACK 报文的定义在 [rfc4585] 文档中定义。 RTCP 的反馈报文包头定义如下&#x…

webrtc nack实现原理

1.nack 简介 webrtc 中nack是最基本的QOS策略&#xff0c;与ack机制不同的地方是nack是接收端检测到丢包时&#xff0c;告知发送端具体丢包的序号&#xff0c;接收端收到nack后从缓存中找到对应的包并发送出去。 2. nack实现 nack rtcp报文格式如上图所示&#xff0c;pt205。P…

webrtc QOS方法一(NACK实现)

一&#xff1a;概述 NACK则在接收端检测到数据丢包后&#xff0c;发送NACK报文到发送端&#xff1b;发送端根据NACK报文中的序列号&#xff0c;在发送缓冲区找到对应的数据包&#xff0c;重新发送到接收端。NACK需要发送端发送缓冲区的支持&#xff0c;RFC5104[2]定义NACK数据包…

认识网络通信中的 ACK、NACK 和 REX

ACK、NACK、 REX在面试或者网络通信的时候&#xff0c;我们可能经常听到和遇到。今天就来详细介绍一下ACK、NACK、 REX。 认识ACK、NACK、 REX ACK&#xff1a;Acknowledgement&#xff0c;它是一种正向反馈&#xff0c;接收方收到数据后回复消息告知发送方。NACK&#xff1a…

WebRTC之NACK、RTX 在什么时机判断丢包发送NACK请求和RTX丢包重传

WebRTC之NACK、RTX 在什么时机判断丢包发送NACK请求和RTX丢包重传 WebRTC之NACK、RTX 在什么时机判断丢包发送NACK请求和RTX丢包重传 WebRTC之NACK、RTX 在什么时机判断丢包发送NACK请求和RTX丢包重传前言一、NACK与RTX的作用1、NACK/RTX的工作机制的流程图2、NACK/RTX涉及到的…

WebRTC 的音频弱网对抗之 NACK

本文梳理 WebRTC 的音频弱网对抗中的 NACK 机制的实现。音频的 NACK 机制在 WebRTC 中默认是关闭的&#xff0c;本文会介绍开启 NACK 机制的方法。 在网络数据传输中&#xff0c;NACK (NAK&#xff0c;negative acknowledgment&#xff0c;not acknowledged) 是数据接收端主动…

流媒体弱网优化之路(NACK)——纯NACK方案的优化探索

流媒体弱网优化之路(NACK)——纯NACK方案的优化探索 —— 我正在的github给大家开发一个用于做实验的项目 —— github.com/qw225967/Bifrost目标&#xff1a;可以让大家熟悉各类Qos能力、带宽估计能力&#xff0c;提供每个环节关键参数调节接口并实现一个json全配置&#xff…

WebRTC源码分析 nack详解

1、Nack过程 1.1 nack是什么 丢包重传(NACK)是抵抗网络错误的重要手段。NACK在接收端检测到数据丢包后&#xff0c;发送NACK报文到发送端&#xff1b;发送端根据NACK报文中的序列号&#xff0c;在发送缓冲区找到对应的数据包&#xff0c;重新发送到接收端。NACK需要发送端&am…