写点什么

验证邮箱有效性的反向操作

作者:为自己带盐
  • 2024-01-23
    河北
  • 本文字数:2890 字

    阅读完需:约 9 分钟

验证邮箱有效性的反向操作

背景

一般来说,我们在系统中要验证用户邮箱有效性的常规做法,是向用户的邮箱发送一个验证码,当用户接收到该验证码并输入系统,就完成了有效性验证。此邮箱就可以作为账户邮箱接收系统的各类推送了。

同样的,手机验证码也是一样的道理。

但有一些场景,我们通过这种常规操作可能会造成操作的复杂性,比如,我们的用户是一些小学生,尽管有家长在帮忙操作,但也有部分家长在操作的时候是盲目的,他们需要一种引导性强,没有心智负担,便利的操作方式。在我遇到的场景里就是,不是所有用户都愿意去接收验证码还要输入到系统里,而作为服务方,邮箱暂时是必须输入且要尽可能保证有效,也就是邮箱虽然是必填项,但如果用户输入了无效的邮箱也是可以继续进行其他操作的,只是无法通过邮箱接收信息,但其他接受渠道是不受影响。

在这种前提下,可以试试反向操作这种做法。

也就是用户再自行输入完邮箱后,由用户主动在系统界面上向某收信邮箱发送验证邮件,当收信邮箱收到邮件后,即验证了该邮箱的有效性,再通过系统内部的消息机制完成后续的验证流程。


其实有些系统在验证手机有效性的时候也是类似的方法,通过向某个指定号码发送一个数字代码,发送完成后在页面上点击已发送,就完成手机号有效性的验证

邮箱协议

邮箱相关的协议主要有,STMP,POP3,IMAP3 种,其中 STMP 协议是发信协议,POP3 和 IMAP 协议都是收信协议,不同的是,IMAP 协议相对更加完善,更灵活,它在邮箱服务器和客户端之间进行双向通信,可以在不下载邮件的前提下在客户端预览邮件的摘要,发信人等基本信息。

其他主要区别如下图

👆图出处:https://zhuanlan.zhihu.com/p/425136361


我们在使用一些邮箱客户端的时候,会进行一些服务器配置,来完成一个终端收发多个邮箱的目的。

👇这是我在邮箱大师配置的 qq 邮箱和工作邮箱


这两个邮箱的配置区别有

  • qq 邮箱的收信用的是 IMAP 协议,工作邮箱用的是 POP3

  • qq 邮箱的收/发信服务器,均启用了 ssl/tls 安全配置,而工作邮箱都没有安全配置

了解了邮箱协议的一些内容,我们就可以考虑通过使用程序手段来监听收信邮箱,从而完成客户邮箱验证。

实现代码

这里我用到的时 Google 开源的 MailKit 组件,它是一个跨平台的邮件客户端库,在 dotnet 环境里,比 c#标准库里自带的 System.Net.Mail 类库(用自带的类库也是可以实现邮件首发的),要更加方便。

这里只介绍 POP3 和 IMAP 协议下邮件的收取,邮件发送相关的方法很多,不在赘述。

/// <summary>/// 收信,imap(新邮箱基本都支持,接口更灵活,更丰富)/// </summary>/// <param name="subject"></param>/// <returns></returns>public async Task<bool> ReviceEmailAsync(string subject,string memberEmail){	try	{		using (var client = new ImapClient())		{			//设定60s超时			var TokenSource = new CancellationTokenSource(60000);			//根据需要觉得是否开启SSL/TSL			client.Connect("<支持IMAP协议的收信服务>", 993, true);			await client.AuthenticateAsync("<邮箱地址>, "<客户端授权码>");						//网易163/126邮箱闭坑(UnSafe login之类的错误)			//解决办法参见-->https://www.cnblogs.com/peterYong/p/15000793.html#_label2_0			var clientImp = new ImapImplementation			{				Name = "EXAMINE",				Version = "2.0"			};			client.Identify(clientImp);
var inbox = client.Inbox; inbox.Open(MailKit.FolderAccess.ReadOnly, cancellationToken: TokenSource.Token); //查找1天内包含该关键字的邮件 var query = SearchQuery.DeliveredAfter(DateTime.Now.AddDays(-1)).And(SearchQuery.SubjectContains(subject)); var result = await inbox.SearchAsync(query); if(!result.Any()) { return false; } //发信模板 string tmpTxt = System.IO.File.ReadAllText(System.IO.Path.Combine(Environment.CurrentDirectory, "wwwroot", "tamplate", "email.txt")); string content = tmpTxt.Replace("?email?", memberEmail).Replace("?projectno?", subject); //回一封结果邮件 await SendEmailKitAsync(memberEmail, "邮箱验证", content); client.Disconnect(true); return true; } } catch (Exception ex) { Console.WriteLine($"Failed to revice {ex.Message}"); return false; }}
/// <summary>/// 收信,pop3(部分邮箱服务器不支持imap,只能退而求其次选择pop)/// </summary>/// <param name="subject"></param>/// <param name="memberEmail"></param>/// <returns></returns>public async Task<bool> ReviceEmailPopAsync(string subject, string memberEmail){ try { using (var client = new Pop3Client()) { var TokenSource = new CancellationTokenSource(60000); client.Connect("<支持POP协议的收信服务>", 110); await client.AuthenticateAsync("<邮箱地址>", "<客户端授权码>"); //设定的筛选条件(按需修改) if(!client.Any(x=>x.Subject==subject && x.Priority==MessagePriority.Urgent && x.MessageId== "<指定的id>" && x.Date > DateTime.Now.AddDays(-1))) { return false; }
var result = client.Where(x => x.Subject.Contains(subject) && x.Date > DateTime.Now.AddDays(-1)).FirstOrDefault(); //发信模板 string tmpTxt = System.IO.File.ReadAllText(System.IO.Path.Combine(Environment.CurrentDirectory, "wwwroot", "tamplate", "email.txt")); string content = tmpTxt.Replace("?email?", memberEmail).Replace("?projectno?", subject); //回一封邮件 await SendEmailKitAsync(memberEmail, "邮箱验证", content);
client.Disconnect(true); return true;
} } catch (Exception ex) { Console.WriteLine($"Failed to revice {ex.Message}"); return false; }}
复制代码


在实际的使用过程,还可以进一步优化,比如已经验证有效的邮箱地址,将其存入数据库或者缓存当中,作为白名单,再次需要验证时,先比对白名单,比对通过后则直接验证通过。

调用时的代码差不多可以这样写

public async Task<IActionResult> EmailTest([FromServices]IEmailKitHelper emailKitHelper, [FromServices] IRedisCachingProvider redisCachingProvider,string subject,string email){    if (await redisCachingProvider.HExistsAsync("AvailableEmails", email))    {      	        return Json(new { code = 0, msg = "邮箱已通过验证" });    }    if(await emailKitHelper.ReviceEmailViaImapAsync(subject,email))    {        await redisCachingProvider.HSetAsync("AvailableEmails", email, DateTime.Now.ToString("yyyyMMdd") + "," + subject);    }    return Json(new { code = 0, msg = "邮箱已通过验证并加入到可用邮箱列表" });}
复制代码

收到的验证通过的效果

前台页面接收到接口返回的结果后,可以根据情况修改页面的渲染效果即可,相关代码不再赘述。

发布于: 刚刚阅读数: 5
用户头像

学着写代码 2019-04-11 加入

是一枚,热爱技术,天赋不高,又有点轴,的猿。。

评论

发布
暂无评论
验证邮箱有效性的反向操作_dotnetcore_为自己带盐_InfoQ写作社区