这是一篇系列博文,我将使用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 手机号免密登录验证与号码绑定功能的实现(一):验证码模块相关推荐

  1. Abp.Zero 手机号免密登录验证与号码绑定功能的实现(二):改造Abp默认实现

    接下来我们重写原Abp的部分实现,来驳接手机号相关业务. 改造User类 重写PhoneNumber使得电话号码为必填项,和中国大陆手机号11位长度 public new const int MaxP ...

  2. APP新用户注册、手机号绑定、用户登录验证新方式——一键登录(免密登录)验证方式新趋势

    传统的手机APP应用注册通常需要用户输入用户名,填写用户密码,同时通过获取验证码绑定手机号.用户一旦注册后再次登录传统的方式都是提供用户名加密码,或者凭手机验证码登录.当然现在许多APP为了获取更多的 ...

  3. 验证ssh免密登录_linux ssh 免密登录

    转载请务必注明原创地址为:https://dongkelun.com/2018/04/05/sshConf/ 以下用三台centos为例,ip分别为192.168.44.138.192.168.44. ...

  4. flask框架如何实现修改密码和免密登录功能

    flask是python web开发的常用框架之一.本文将讲述flask如何实现修改密码和免密登录功能 修改密码功能 数据库部分: #重置密码 def reset_pass(phone,passwor ...

  5. 中国移动“一键免密登录”:免去注册登录的烦恼

    对于你而言,手机号码意味着什么?联系你的唯一方式,众多App的绑定号码,还是银行预留号?很多人不愿意更换手机号都是出于以上理由.在移动互联网发展如此迅速的今天,手机号几乎已经成为了个人在网上的身份证明 ...

  6. cookie 免密登录_python

    我们都知道 HTTP 是无状态的,用户每次打开 web 页面时,服务器都打开新的会话,而且服务器也不会自动维护客户的上下文信息,那么服务器是怎么识别用户的呢? 这就是本文今天要讲解的内容.当服务端需要 ...

  7. ssh免密登录linux服务器

    Ssh免密登录 sshd服务 sshd简介: SSH 密钥为登录 Linux 服务器提供了更好且安全的机制.运行 ssh-keygen 后,将会生成公私密钥对.你可以将公钥放置到任意服务器,从持有私钥 ...

  8. Hadoop-2.8.0集群搭建、hadoop源码编译和安装、host配置、ssh免密登录、hadoop配置文件中的参数配置参数总结、hadoop集群测试,安装过程中的常见错误

    25. 集群搭建 25.1 HADOOP集群搭建 25.1.1集群简介 HADOOP集群具体来说包含两个集群:HDFS集群和YARN集群,两者逻辑上分离,但物理上常在一起 HDFS集群: 负责海量数据 ...

  9. kubernetes1.8.4安装指南 -- 2. ssh免密登录

    实现kubernetes master到node的免密登录,以下操作只需要在master运行. 生成密钥对 ssh-keygen 复制ssh公钥到远程主机,这样ssh到远程主机的时候不需要输入密码 s ...

最新文章

  1. 【机器学习】PyCaret!又一个神仙的自动机器学习库!
  2. ​ RROR 1221 (HY000): Incorrect usage of spatial/fulltext/hash index and explicit index orde ​
  3. Chrome 浏览器扩展 - Night Eye
  4. FCS省选模拟赛 Day7
  5. 与敏捷团队一起交付价值
  6. python汉语读音_【学习】python 汉语转拼音
  7. Windows 键盘快捷键 : Windows 快捷键
  8. mysql创建外键失败_mysql创建外键错误
  9. 如何配置Ubuntu 16.04 GRUB 2引导加载程序
  10. Scrapy爬虫框架的安装和使用
  11. c语言中常用函数名,C语言中一些常用函数的说明
  12. DTC Sprint总结——管理经验篇
  13. php 加减法,php加减法
  14. php里用钢笔画曲线,ps钢笔工具怎么抠图
  15. asp毕业设计——基于asp+access的订单管理系统设计与实现(毕业论文+程序源码)——订单管理系统
  16. 【絮叨.2】PRD才是高级语言,程序员就是产品的编译器
  17. 写一个判断素数的函数,在主函数输入一个整数,输出是否为素数的信息
  18. 分享Silverlight/Windows8/WPF/WP7/HTML5一周学习导读(5月21日-5月26日)
  19. BUAA离散数学第十章 图论 最短路径及关键通路 python解法
  20. GPS 定位模块数据提取

热门文章

  1. 自适应螺旋飞行麻雀搜索算法
  2. 进程和线程上下文切换_编码人员:上下文切换对于计算机和关系而言都很难
  3. 如何制作一个自定义的winpe?
  4. ruoyi自定义工作流(前端二,添加流程管理页面)
  5. python中docx模块的使用_Python-使用.docx模块
  6. myql 查询树形表结果:说说、说说的评论、评论的回复
  7. [RL] 深入理解Tabular Leaning (MC/TD) 过程中的梯度下降使用
  8. ImageMagick快速入门
  9. 一文详解AIGC:推动元宇宙发展的加速器
  10. c++算法——枚举法