一个合规、安全、可靠的短信验证码项目模块该当具备以下几点特色:
发送的验证码存在一定韶光的有效期验证码不宜过长或过短同一手机号码不能频繁发送验证码要求验证码被利用后就失落效这个Captcha项目,适值都符合这些特色。
让
using System;using System.Collections.Generic;using System.Text;namespace Captcha.Util{ /// <summary> /// 短信验证码工具类 /// </summary> public static class MsgCaptchaHelper { /// <summary> /// 天生指定位数的随机数字码 /// </summary> /// <param name="length"></param> /// <returns></returns> public static string CreateRandomNumber(int length) { Random random = new Random(); StringBuilder sbMsgCode = new StringBuilder(); for (int i = 0; i < length; i++) { sbMsgCode.Append(random.Next(0, 9)); } return sbMsgCode.ToString(); } }}
十分清晰,便是利用随机函数天生知足长度哀求的验证码,这里面长度是根据参数传入的,
接着便是紧张逻辑实现的service层

using Captcha.Dto;using Captcha.Service.Contract;using Captcha.Util;using Microsoft.AspNetCore.Hosting;using Microsoft.Extensions.Caching.Memory;using System;using System.Collections.Generic;using System.Text;namespace Captcha.Service{ public class CaptchaService : ICaptchaService { #region Private Fields private readonly IMemoryCache _cache; private readonly IHostingEnvironment _hostingEnvironment; #endregion #region Constructors public CaptchaService(IMemoryCache cache, IHostingEnvironment hostingEnvironment) { _cache = cache; _hostingEnvironment = hostingEnvironment; } #endregion #region Public Methods /// <summary> /// 获取图片验证码 /// </summary> /// <param name="imgCaptchaDto">图形验证码要求信息</param> /// <returns></returns> public CaptchaResult GetImageCaptcha(ImgCaptchaDto imgCaptchaDto) { var captchaCode = ImageCaptchaHelper.GenerateCaptchaCode(); var result = ImageCaptchaHelper.GenerateCaptcha(100, 36, captchaCode); _cache.Set($"ImgCaptcha{imgCaptchaDto.ImgCaptchaType}{imgCaptchaDto.Mobile}", result.CaptchaCode); return result; } /// <summary> /// 验证图片验证码 /// </summary> /// <param name="imgCaptchaDto">图形验证码信息</param> /// <returns></returns> public bool ValidateImageCaptcha(ImgCaptchaDto imgCaptchaDto) { var cachedImageCaptcha = _cache.Get<string>($"ImgCaptcha{imgCaptchaDto.ImgCaptchaType}{imgCaptchaDto.Mobile}"); if (string.Equals(imgCaptchaDto.ImgCaptcha, cachedImageCaptcha, StringComparison.OrdinalIgnoreCase)) { return true; } else { return false; } } /// <summary> /// 获取短信验证码 /// </summary> /// <param name="msgCaptchaDto">短信验证码要求信息</param> /// <returns></returns> public (bool, string) GetMsgCaptcha(MsgCaptchaDto msgCaptchaDto) { if (string.IsNullOrWhiteSpace(msgCaptchaDto.ImgCaptcha)) { throw new BusinessException((int)ErrorCode.BadRequest, "请输入图形验证码"); } var cachedImageCaptcha = _cache.Get<string>($"ImgCaptcha{msgCaptchaDto.MsgCaptchaType}{msgCaptchaDto.Mobile}"); if (!string.Equals(msgCaptchaDto.ImgCaptcha, cachedImageCaptcha, StringComparison.OrdinalIgnoreCase)) { return (false, "验证失落败,请输入精确手机号及获取到的图形验证码"); } string key = $"MsgCaptcha{msgCaptchaDto.MsgCaptchaType}{msgCaptchaDto.Mobile}"; var cachedMsgCaptcha = _cache.Get<MsgCaptchaDto>(key); if (cachedMsgCaptcha != null) { var offsetSecionds = (DateTime.Now - cachedMsgCaptcha.CreateTime).Seconds; if (offsetSecionds < 60) { return (false, $"短信验证码获取太频繁,请{60 - offsetSecionds}秒之后再获取"); } } var msgCaptcha = MsgCaptchaHelper.CreateRandomNumber(6); msgCaptchaDto.MsgCaptcha = msgCaptcha; msgCaptchaDto.CreateTime = DateTime.Now; msgCaptchaDto.ValidateCount = 0; _cache.Set(key, msgCaptchaDto, TimeSpan.FromMinutes(2)); if (_hostingEnvironment.IsProduction()) { //TODO:调用第三方SDK实际发送短信 return (true, "发送成功"); } else //非生产环境,直接将验证码返给前端,便于调查跟踪 { return (true, $"发送成功,短信验证码为:{msgCaptcha}"); } } /// <summary> /// 验证短信验证码 /// </summary> /// <param name="msgCaptchaDto">短信验证码信息</param> /// <returns></returns> public (bool, string) ValidateMsgCaptcha(MsgCaptchaDto msgCaptchaDto) { var key = $"MsgCaptcha{msgCaptchaDto.MsgCaptchaType}{msgCaptchaDto.Mobile}"; var cachedMsgCaptcha = _cache.Get<MsgCaptchaDto>(key); if (cachedMsgCaptcha == null) { return (false, "短信验证码无效,请重新获取"); } if (cachedMsgCaptcha.ValidateCount >= 3) { _cache.Remove(key); return (false, "短信验证码已失落效,请重新获取"); } cachedMsgCaptcha.ValidateCount++; if (!string.Equals(cachedMsgCaptcha.MsgCaptcha, msgCaptchaDto.MsgCaptcha, StringComparison.OrdinalIgnoreCase)) { return (false, "短信验证码缺点"); } else { return (true, "验证通过"); } } #endregion }}
这里不得不夸一句,项目比
这里利用到ImageCaptchaHelper.GenerateCaptchaCode();这个方法,听说是一个现成的图形校验天生方法,是一个名叫Edi Wang的大神开源供应的。
实话说,
if (string.IsNullOrWhiteSpace(msgCaptchaDto.ImgCaptcha)) { throw new BusinessException((int)ErrorCode.BadRequest, "请输入图形验证码"); } var cachedImageCaptcha = _cache.Get<string>($"ImgCaptcha{msgCaptchaDto.MsgCaptchaType}{msgCaptchaDto.Mobile}"); if (!string.Equals(msgCaptchaDto.ImgCaptcha, cachedImageCaptcha, StringComparison.OrdinalIgnoreCase)) { return (false, "验证失落败,请输入精确手机号及获取到的图形验证码"); }
同时,service还实现了
return (false, $"短信验证码获取太频繁,请{60 - offsetSecionds}秒之后再获取");
在验证阶段,程序完成了对缓存中验证码是否存在的校验,是否利用过的校验,像这个例子里面,是将利用次数设定为3次,如果超过3次的才会被认定无效,如果想严谨点的,可以直接设为1次。
if (cachedMsgCaptcha == null) { return (false, "短信验证码无效,请重新获取"); } if (cachedMsgCaptcha.ValidateCount >= 3) { _cache.Remove(key); return (false, "短信验证码已失落效,请重新获取"); } cachedMsgCaptcha.ValidateCount++; if (!string.Equals(cachedMsgCaptcha.MsgCaptcha, msgCaptchaDto.MsgCaptcha, StringComparison.OrdinalIgnoreCase)) { return (false, "短信验证码缺点"); } else { return (true, "验证通过"); }
整体的运行逻辑,实在都在service层完成了。小伙伴想额外增加其他校验的话也可以在这段逻辑里面自行增加,总的来说呢,这个项目逻辑清晰,即插即用,扩展性也不错,也非常适宜想学习的小伙伴明白一个短信验证码从天生、发送、校验、生效通过这样一个完全的链路。
随着互联网的发展,光靠大略的密码密钥很难确保安全,短信验证想必会越来越遍及,想学习理解的小伙伴,乘着假期赶紧来学习一波吧~关注我,私信“短信验证”,获取仓库地址~