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位验证码也很容易被破解,因此需要考虑这些安全问题。在实际生产代码中,验证的参数常用手机号+验证码做哈希运算保证安全。
项目地址
Abp.Zero 手机号免密登录验证与号码绑定功能的实现(一):验证码模块的更多相关文章
- linux免密登录ssh验证配置方法及常见错误解决
目标:从服务器A免密登录服务器B [配置方法] 1.在服务器A生成密钥文件,直接使用以下命令: ssh-keygen 中间遇到输入内容一路回车即可,完成后会在 ~/.ssh 目录下生成两个文件:id_ ...
- 1.配置桥接,并抓包验证 2.实现免密登录 3.修改登录端口: 22-》2222 4.不允许root用户远程登录 5.创建用户sshuser1,并设置密码,且只允许sshuser1远程ssh登录
1.配置桥接: 抓包时如果有ens160的ICMP,说明我们的桥接搭建成功通过桥接访问到了ens160(这里忘加图片了) (1)创建一个桥接设备和会话 (2)添加设备和会话到桥接设备上 (3)启动从 ...
- 基于vagrant工具在win7下免密登录linux
一.SSH加密方式 SSH采用的是"非对称密钥系统",即耳熟能详的公钥私钥加密系统,其安全验证又分为两种级别. 1. 基于口令的安全验证 这种方式使用用户名密码进行联机登录,一般情 ...
- linux(十)配置ssh免密登录实现
知道ssh的朋友应该知道它是用来干什么的,如果你不知道什么是ssh远程登录的话,可以去看一下我的上一篇博客,关于linux的网络基础的知识.备注:ssh是用于远端登入.执行ssh指令开启终端机阶段作业 ...
- 【图文详解】linux下配置远程免密登录
linux下各种集群搭建往往需要配置远程免密登录,本文主要描述了CentOs6.3系统下配置免密登录的详细过程. ssh远程登录,两种身份验证: 用户名+密码 密钥验证 机器1生成密钥对并将公钥发给机 ...
- (11)ssh免密登录配置
***在Linux命令行中登录到另一台虚拟机(需要用到ssh协议) Linux中默认有ssh的服务器端和客户端,客户端的名字就叫ssh 前提是当前使用的用户名在待连接的虚拟机中存在 格式: ssh ...
- CentOS7+CDH5.14.0安装全流程记录,图文详解全程实测-2设置SSH免密登录
因为hadoop集群在安装的时候需要集群中所有机器的权限. 所以我们需要打通所有节点的ssh无密码登陆,思路是生成每台机子的密钥,集中在一个文件中,再分发到每台机子上. 为了确保下面的命令能顺利执行, ...
- Ubuntu 开启SSH服务以及有关设置:安装,指定端口号、免密登录、远程拷贝
本文所用系统为 Ubuntu 18.04 什么是SSH? 简单说,SSH是一种网络协议,用于计算机之间的加密登录.全名为:安全外壳协议.为Secure Shell的缩写.SSH为建立在应用 ...
- linux上ssh免密登录原理及实现
因为我的服务器集群需要回收日志到中央进行统一处理,所以需要建立ssh互信关系实现免密登录.关于ssh的使用大家可能都很熟悉了,我们今天主要来讲下ssh连接和免密登录的原理. scp 传输文件 scp( ...
随机推荐
- Word 文字多选方式有哪些?
Ctrl + 鼠标左键:不连续地选择文字. Shift + 鼠标左键:连续地选择文字. Alt + 鼠标左键:自由选择文字.
- Excel 查找函数(一):LOOKUP
序号 员工姓名 部门 职务 1 苏霞 法务部 法律顾问 2 包志林 财务部 财务总监 3 林娥云 安监部 部长 4 石少卿 质检部 质检员 5 于炳福 生产部 生产部 6 蒋琼志 仓储部 保管员 7 ...
- OpenCV CMake VSCode Windows 平台下运行配置及其解决方案
前言 最近在搞 计算机图形学相关的东西,有个 demo 用到了 opencv,找了 google 一圈,发现国内都没有比较好的配置和解决的办法,要不就是几年前的教程,最近正好踩坑完,其中经历了自己编译 ...
- Openstack之各组件命令
openstack 组件命令 Glance组件操作(镜像服务组件) #环境配置: source /etc/keystone/admin-openrc.sh #查看镜像列表: glance image- ...
- 引擎之旅 前传:C++代码规范
自己以前写代码时,一个项目一个风格.单人开发的工作使得我并没有注意到代码规范性和可读性的问题.每当项目结束后,看到自己杂乱无章的代码,完全没有二次开发和重构的欲望. 写代码就应该像写诗一样优雅. by ...
- byte[]数组转换string类型
byte[] OutData = new byte[2048];//交易返回数据 string pBusiCardInfoStr = Encoding.Default.GetString(OutDat ...
- 【Azure 环境】Azure Resource Graph Explorer 中实现动态数组数据转换成多行记录模式 - mv-expand
问题描述 想对Azure中全部VM的NSG资源进行收集,如果只是查看一个VM的NSG设定,可以在门户页面中查看表格模式,但是如果想把导出成表格,可以在Azure Resource Graph Expl ...
- 基于koa模块和socket.io模块搭建的node服务器实现通过jwt 验证来渲染列表、私聊、群聊功能
1. 具体代码在需要的下载 https://gitee.com/zyqwasd/socket 效果: 2. package.json文件 1. 下载基本的模块 修改了start 脚本 nodemo ...
- Windows 2012 R2 计划任务发送邮件
这两天把域控制器升级到了2012 R2,忽然发现原本用的系统自动发邮件提示用户账户锁定的计划任务配置起来有点麻烦了.原因是微软把自动发送邮件和提示消息的功能从计划任务中去除了. 首先用wevtu ...
- Dapr 证书过期了怎么办? 别慌,有救!
一.背景 Dapr 默认证书有效时间是1年,证书过期后就不能执行相关控制面和数据面的交互了,如下图: 二.查看证书有效时间 通过dapr mtls expiry 看到期时间,具体参见命令https:/ ...