文章目录

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

在之前的博文 用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

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

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

    文章目录 发送验证码 登录 退出登录 界面控件 获取用户信息功能 项目地址 前端代码的框架采用vue.js + elementUI 这套较为简单的方式实现,以及typescript语法更方便阅读. 首 ...

  2. 两步验证杀手锏:Java 接入 Google 身份验证器实战

    转载自   两步验证杀手锏:Java 接入 Google 身份验证器实战 什么是两步验证? 大家应该对两步验证都熟悉吧?如苹果有自带的两步验证策略,防止用户账号密码被盗而锁定手机进行敲诈,这种例子屡见 ...

  3. 苹果再遭诉讼!因两步验证太过耗时...

    限于iPhone XS系列新品的售价昂贵,创新度不足,苹果手机正在遭遇着前所未有的低迷状况,然而,即便硬件不给力,一般意义上来看,苹果在iOS系统上的优势还是存在的.但仍然有消费者不买账. 近期,名为 ...

  4. 三型两步验证登不上_苹果手机开启了双重认证,未信任的设备之前登入了,开启了双重认证未...

    展开全部 首先需要明确的一个概念:「62616964757a686964616fe58685e5aeb931333365643661双重认证」不是「两步验证」. 「双重认证」与「两步验证」的区别 尽管 ...

  5. 两步验证 非双重认证

    Two-factor authentication must be turned on for your Apple ID. After you turn it on, signing into yo ...

  6. apple id两步验证服务器,如何开启 Apple ID 两步验证

    本期教学点: 开启 Apple ID 两步验证,减小 Apple ID 被盗风险,大大提高安全性. 首先打开 Safari 浏览器,在地址栏输入 appleid.apple.com. 点击右边&quo ...

  7. Google账户两步验证的工作原理【转】

    最近在考虑一些用户登录验证的问题,现阶段在涉及到一些交易时,基本上都使用的是短信验证码验证,但有朋友说,有些时候短信验证码会出现延时,不及时.于是就看了一下Google Authenticator(c ...

  8. Gmail设置两步验证密码后,foxmail收不到gmail邮件

    最近直接访问gmail.com可以登录gmail邮箱,但是用outlook或者foxmail登录不上,一直提示密码错误.原因很简单,就是你设置了google的两步验证密码.从这上面着手. 你需要使用应 ...

  9. Apple ID 两步验证 诡异中招 怀疑苹果服务器问题

    账户密码突然不对了, 两步验证密钥无效, 苹果官方不给解决 设备在这段时间没有任何变更 id开启了两步验证 发生问题时是store上下app提示输入密码错误 上apple官网登录账户,密码错误 点忘记 ...

最新文章

  1. 【干货】JDK动态代理的实现原理以及如何手写一个JDK动态代理
  2. Python单元测试框架之pytest---如何执行测试用例
  3. 第一阶段个人总结03
  4. 第八篇——Struts2的处理结果类型
  5. docker报错:OCI runtime create failed...process_linux.go:449: container init caused “write /proc/self/
  6. linux挂载wondiws目录,linux cifs自动挂载windows硬盘或文件夹
  7. java e.getmessage() null_Java e.getMessage 错误信息为null
  8. linux进程管理 pdf,高效与精细的结合--Linux的进程管理.pdf
  9. 【theano-windows】学习笔记二——theano中的函数和共享参数
  10. pythoninit_Python __init__.py文件的作用
  11. JPA EntityManager –HibernateEntityManager
  12. 全链路压测及阿里全链路压测详解
  13. 智慧城市智慧园区智慧路灯方案介绍
  14. 计算机专业毕业论文写作指导(案例超详解)
  15. oracle11g本地安装,windows64位机oracle11g+pl/sql安装教程图解
  16. 中国乡镇企业会计杂志中国乡镇企业会计杂志社中国乡镇企业会计编辑部2022年第12期目录
  17. Java实现二手交易系统
  18. 华为云服务-应用部署2-创建环境到创建应用
  19. 2021钳工技能高考成绩查询,这里有2021钳工时间和报名费用以及流程
  20. D. Sequence and Swaps

热门文章

  1. matlab实现幅度调制,利用matlab实现信号幅度的调制与解调钟媛
  2. 【20211217】【信号处理】从 Matlab 仿真角度理解栅栏效应
  3. 拉格朗日函数、拉格朗日对偶问题、KKT条件个人理解
  4. Cadence OrCAD Capture CIS使用Excel协助DRC检查网络名称低级错误的技巧图文教程
  5. Python 倒计时滴滴滴 语言提醒
  6. 关于Web软件的界面设计——《Web软件用户界面设计指南》
  7. java的scope_spring中@Scope作用域的注解
  8. JClaim --Java IM 客户端框架
  9. 【ESP32学习笔记】#通用篇#(1)简介及资料
  10. 【IDL读取txt文件】