Abp.Zero 手机号免密登录验证与号码绑定功能的实现(一):验证码模块
这是一篇系列博文,我将使用Abp.Zero搭建一套集成手机号免密登录验证与号码绑定功能的用户系统:
- Abp.Zero 手机号免密登录验证与号码绑定功能的实现(一):验证码模块
- Abp.Zero 手机号免密登录验证与号码绑定功能的实现(二):改造Abp默认实现
- Abp.Zero 手机号免密登录验证与号码绑定功能的实现(三):网页端开发
第三方身份验证在Abp中称之为外部身份验证(ExternalAuthentication), 区别于Abp的外部身份授权(ExternalAuth),这里Auth的全称应为Authorization,即授权。
首先来厘清这两个不同的业务在Abp中的实现,我之前写的这篇 Abp.Zero 搭建第三方登录模块 系列文章中描述的业务,即使用的Abp外部身份授权(ExternalAuth)的相关扩展而实现的。还记得我们实现的WeChatAuthProvider吗?它继承于ExternalAuthProviderApi这个抽象类,实现的微信授权功能。所以微信登录这个动作,实际是在授权(Authorization)已有的微信账号,访问服务端资源,而身份验证(Authentication)步骤,已在其他端完成了(手机微信扫码),在服务端获取已验证好身份的第三方账户并生成Token则可以抽象的认为是授权(Authorization)行为。
所以“搭建第三方登录模块”应该更准确地描述为“第三方授权模块”。
从Abp接口设计上,也能看得出来两者的差别。
外部身份验证(ExternalAuthentication)关注的是校验,实现TryAuthenticateAsync并返回是否成功,而CreateUserAsync和UpdateUserAsync仅是校验流程里的一部分,不实现它并不影响身份验证结果,外部授权源的接口定义如下,
public interface IExternalAuthenticationSource<TTenant, TUser> where TTenant : AbpTenant<TUser> where TUser : AbpUserBase
{...Task<bool> TryAuthenticateAsync(string userNameOrEmailAddress, string plainPassword, TTenant tenant);Task<TUser> CreateUserAsync(string userNameOrEmailAddress, TTenant tenant);Task UpdateUserAsync(TUser user, TTenant tenant);
}
外部授权(ExternalAuth)这一步关注的业务是拿到外部账号,如微信的OpenId,所以IExternalAuthManager重点则是GetUserInfo,而IsValidUser并没有在默认实现中使用到
public interface IExternalAuthManager
{Task<bool> IsValidUser(string provider, string providerKey, string providerAccessCode);Task<ExternalAuthUserInfo> GetUserInfo(string provider, string accessCode);
}
然而这些是从LoginManager原本实现看出的,我们可以重写这个类原本的方法,加入电话号码的处理逻辑。
在搞清楚这两个接口后,相信你会对Abp用户系统的理解更加深刻
短信获取验证码来校验,是比较常用的第三方身份验证方式,今天来做一个手机号码免密登录,并且具有绑定/解绑手机号功能的小案例,效果如图:
示例代码已经放在了GitHub上:Github:matoapp-samples
用户验证码校验模块
首先定义DomainService接口,我们将实现手机验证码的发送、验证码校验、解绑手机号、绑定手机号
这4个功能,并且定义用途以校验行为合法性,和用它来区分短信模板
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");
}
public const string LOGIN = "LOGIN";public const string IDENTITY_VERIFICATION = "IDENTITY_VERIFICATION";public const string BIND_PHONENUMBER = "BIND_PHONENUMBER";public const string UNBIND_PHONENUMBER = "UNBIND_PHONENUMBER";
定义一个验证码Token缓存管理类,以及对应的缓存条目类,用于承载验证码的校验内容
public class SmsCaptchaTokenCache : MemoryCacheBase<SmsCaptchaTokenCacheItem>, ISingletonDependency
{public SmsCaptchaTokenCache() : base(nameof(SmsCaptchaTokenCache)){}
}
缓存条目将存储电话号码,用户Id(非登录用途)以及用途
public class SmsCaptchaTokenCacheItem
{public string PhoneNumber { get; set; }public long UserId { get; set; }public string Purpose { get; set; }
}
阿里云和腾讯云提供了短信服务Sms,是国内比较常见的短信服务提供商,不需要自己写了,网上有大把的封装好的库,这里使用AbpBoilerplate.Sms作为短信服务库。
创建短信验证码的领域服务类SmsCaptchaManager并实现ICaptchaManager接口,同时注入短信服务ISmsService,用户管理服务UserManager,验证码Token缓存管理服务SmsCaptchaTokenCache
public class SmsCaptchaManager : DomainService, ICaptchaManager
{private readonly ISmsService SmsService;private readonly UserManager _userManager;private readonly SmsCaptchaTokenCache captchaTokenCache;public static TimeSpan TokenCacheDuration = TimeSpan.FromMinutes(5);public SmsCaptchaManager(ISmsService SmsService,UserManager userManager,SmsCaptchaTokenCache captchaTokenCache){this.SmsService=SmsService;_userManager=userManager;this.captchaTokenCache=captchaTokenCache;}
}
新建SendCaptchaAsync方法,作为短信发送和缓存Token方法,CommonHelp中的GetRandomCaptchaNumber()用于生成随机6位验证码,发送完毕后,将此验证码作为缓存条目的Key值存入
public async Task SendCaptchaAsync(long userId, string phoneNumber, string purpose)
{var captcha = CommonHelper.GetRandomCaptchaNumber();var model = new SendSmsRequest();model.PhoneNumbers= 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"};model.TemplateParam= JsonConvert.SerializeObject(new { code = captcha });var result = await SmsService.SendSmsAsync(model);if (string.IsNullOrEmpty(result.BizId) && result.Code!="OK"){throw new UserFriendlyException("验证码发送失败,错误信息:"+result.Message);}await captchaTokenCache.SetAsync(captcha, new SmsCaptchaTokenCacheItem(){PhoneNumber=phoneNumber,UserId=userId,Purpose=purpose}, absoluteExpireTime: DateTimeOffset.Now.Add(TokenCacheDuration));
}
绑定手机号功能实现
public async Task BindAsync(string token)
{SmsCaptchaTokenCacheItem currentItem = await GetToken(token);if (currentItem==null || currentItem.Purpose!=CaptchaPurpose.BIND_PHONENUMBER){throw new UserFriendlyException("验证码不正确或已过期");}var user = await _userManager.GetUserByIdAsync(currentItem.UserId);if (user.IsPhoneNumberConfirmed){throw new UserFriendlyException("已绑定手机,请先解绑后再绑定");}user.PhoneNumber=currentItem.PhoneNumber;user.IsPhoneNumberConfirmed=true;await _userManager.UpdateAsync(user);await RemoveToken(token);
}
解绑手机号功能实现
public async Task UnbindAsync(string token)
{SmsCaptchaTokenCacheItem currentItem = await GetToken(token);if (currentItem==null|| currentItem.Purpose!=CaptchaPurpose.UNBIND_PHONENUMBER){throw new UserFriendlyException("验证码不正确或已过期");}var user = await _userManager.GetUserByIdAsync(currentItem.UserId);user.IsPhoneNumberConfirmed=false;await _userManager.UpdateAsync(user);await RemoveToken(token);}
验证功能实现
public async Task<bool> VerifyCaptchaAsync(string token, string purpose = CaptchaPurpose.IDENTITY_VERIFICATION)
{SmsCaptchaTokenCacheItem currentItem = await GetToken(token);if (currentItem==null || currentItem.Purpose!=purpose){return false;}await RemoveToken(token);return true;
}
实际业务中可能还需要Email验证,我也建立了电子邮箱验证码的领域服务类,只不过没有实现它,动手能力强的读者可以试着完善这个小案例:)
Api实现
AppService层创建CaptchaAppService.cs,并写好接口
public class CaptchaAppService : ApplicationService
{private readonly SmsCaptchaManager captchaManager;public CaptchaAppService(SmsCaptchaManager captchaManager){this.captchaManager=captchaManager;}[HttpPost]public async Task SendAsync(SendCaptchaInput input){await captchaManager.SendCaptchaAsync(input.UserId, input.PhoneNumber, input.Type);}[HttpPost]public async Task VerifyAsync(VerifyCaptchaInput input){await captchaManager.VerifyCaptchaAsync(input.Token);}[HttpPost]public async Task UnbindAsync(VerifyCaptchaInput input){await captchaManager.UnbindAsync(input.Token);}[HttpPost]public async Task BindAsync(VerifyCaptchaInput input){await captchaManager.BindAsync(input.Token);}
}
至此我们就完成了验证码相关逻辑的接口
下一章将介绍如何重写Abp默认方法,以集成手机号登录功能。
注意!不要将本示例作为生产级代码使用
本示例中,验证码校验的接口并没有做严格加密,6位验证码也很容易被破解,因此需要考虑这些安全问题。在实际生产代码中,验证的参数常用手机号+验证码做哈希运算保证安全。
项目地址
Github:matoapp-samples
Abp.Zero 手机号免密登录验证与号码绑定功能的实现(一):验证码模块相关推荐
- Abp.Zero 手机号免密登录验证与号码绑定功能的实现(二):改造Abp默认实现
接下来我们重写原Abp的部分实现,来驳接手机号相关业务. 改造User类 重写PhoneNumber使得电话号码为必填项,和中国大陆手机号11位长度 public new const int MaxP ...
- APP新用户注册、手机号绑定、用户登录验证新方式——一键登录(免密登录)验证方式新趋势
传统的手机APP应用注册通常需要用户输入用户名,填写用户密码,同时通过获取验证码绑定手机号.用户一旦注册后再次登录传统的方式都是提供用户名加密码,或者凭手机验证码登录.当然现在许多APP为了获取更多的 ...
- 验证ssh免密登录_linux ssh 免密登录
转载请务必注明原创地址为:https://dongkelun.com/2018/04/05/sshConf/ 以下用三台centos为例,ip分别为192.168.44.138.192.168.44. ...
- flask框架如何实现修改密码和免密登录功能
flask是python web开发的常用框架之一.本文将讲述flask如何实现修改密码和免密登录功能 修改密码功能 数据库部分: #重置密码 def reset_pass(phone,passwor ...
- 中国移动“一键免密登录”:免去注册登录的烦恼
对于你而言,手机号码意味着什么?联系你的唯一方式,众多App的绑定号码,还是银行预留号?很多人不愿意更换手机号都是出于以上理由.在移动互联网发展如此迅速的今天,手机号几乎已经成为了个人在网上的身份证明 ...
- cookie 免密登录_python
我们都知道 HTTP 是无状态的,用户每次打开 web 页面时,服务器都打开新的会话,而且服务器也不会自动维护客户的上下文信息,那么服务器是怎么识别用户的呢? 这就是本文今天要讲解的内容.当服务端需要 ...
- ssh免密登录linux服务器
Ssh免密登录 sshd服务 sshd简介: SSH 密钥为登录 Linux 服务器提供了更好且安全的机制.运行 ssh-keygen 后,将会生成公私密钥对.你可以将公钥放置到任意服务器,从持有私钥 ...
- Hadoop-2.8.0集群搭建、hadoop源码编译和安装、host配置、ssh免密登录、hadoop配置文件中的参数配置参数总结、hadoop集群测试,安装过程中的常见错误
25. 集群搭建 25.1 HADOOP集群搭建 25.1.1集群简介 HADOOP集群具体来说包含两个集群:HDFS集群和YARN集群,两者逻辑上分离,但物理上常在一起 HDFS集群: 负责海量数据 ...
- kubernetes1.8.4安装指南 -- 2. ssh免密登录
实现kubernetes master到node的免密登录,以下操作只需要在master运行. 生成密钥对 ssh-keygen 复制ssh公钥到远程主机,这样ssh到远程主机的时候不需要输入密码 s ...
最新文章
- 【机器学习】PyCaret!又一个神仙的自动机器学习库!
- ​ RROR 1221 (HY000): Incorrect usage of spatial/fulltext/hash index and explicit index orde ​
- Chrome 浏览器扩展 - Night Eye
- FCS省选模拟赛 Day7
- 与敏捷团队一起交付价值
- python汉语读音_【学习】python 汉语转拼音
- Windows 键盘快捷键 : Windows 快捷键
- mysql创建外键失败_mysql创建外键错误
- 如何配置Ubuntu 16.04 GRUB 2引导加载程序
- Scrapy爬虫框架的安装和使用
- c语言中常用函数名,C语言中一些常用函数的说明
- DTC Sprint总结——管理经验篇
- php 加减法,php加减法
- php里用钢笔画曲线,ps钢笔工具怎么抠图
- asp毕业设计——基于asp+access的订单管理系统设计与实现(毕业论文+程序源码)——订单管理系统
- 【絮叨.2】PRD才是高级语言,程序员就是产品的编译器
- 写一个判断素数的函数,在主函数输入一个整数,输出是否为素数的信息
- 分享Silverlight/Windows8/WPF/WP7/HTML5一周学习导读(5月21日-5月26日)
- BUAA离散数学第十章 图论 最短路径及关键通路 python解法
- GPS 定位模块数据提取
热门文章
- 自适应螺旋飞行麻雀搜索算法
- 进程和线程上下文切换_编码人员:上下文切换对于计算机和关系而言都很难
- 如何制作一个自定义的winpe?
- ruoyi自定义工作流(前端二,添加流程管理页面)
- python中docx模块的使用_Python-使用.docx模块
- myql 查询树形表结果:说说、说说的评论、评论的回复
- [RL] 深入理解Tabular Leaning (MC/TD) 过程中的梯度下降使用
- ImageMagick快速入门
- 一文详解AIGC:推动元宇宙发展的加速器
- c++算法——枚举法