查看原文
其他

道路千万条,安全第一条,国庆教你在家做一个安全可靠的短信验证模块~

TJ TJ君 2021-11-13

国庆佳节,艳阳高照,天天都是个好天气,似乎更加映照着国泰民安四个大字。国庆前,小编给大家分享过一个关于微信头像使用渐变国旗效果的工具,还不会的可以点这里:渐变国旗微信头像来袭,不用你就OUT了!

看到这里,突然有小伙伴可能会跳出来说,不能用啊!

什么?不能用?

原来,事情是这样的,这个工具原本只是让大家抒发一下自己心中的爱国情怀,但是没想到的是,这两天小编陆陆续续听到有一些人在说这样使用国旗犯法!

嗯?那这到底是怎么一回事呢?

其实事情的来龙去脉不难,也不知道是谁,开始在网上散布一条疑似陕西公安发出的短信,大致意思是说:由于你在微信头像中使用国旗背景,导致国旗变形,您已犯《中华人民共和国国旗法》第十八条,请你尽快到咸阳市秦都区派出所进行思想教育,否则我们将采取强制措施,对你进行十五日以下的拘留。

短信的落款是咸阳市秦都区派出所

西安咸阳,十三朝古都啊,大家一看这落款一看这有模有样的描述,自然都害怕了起来。

不过小编要告诉大家阿是:

没错,所谓的咸阳市秦都区派出所短信,就是个谎言、是个谣言!

根据记者采访咸阳市110指挥中心相关负责人的反馈,目前并没有所谓的咸阳市秦都区派出所这个机构,当地公安也并没有做过类似的事情发过类似的短信,告诫大家不要轻信谣言。

其实法律早有规定,《中华人民共和国国旗法》规定:国旗及其图案不得用作商标、授予专利权的外观设计和商业广告,不得用于私人丧事活动等不适宜的情形。

早在几年前,就有官微明确表示,只要不是带有恶意,用国旗做头像并不违法。

所以大家可以放心地用小编的工具,展示自己的爱国之情,不过需要注意的是使用时切记保持国旗的完整性,不能对国旗进行涂划、玷污等行为即可。

其实从这件事情,我们可以看出,国民日渐普及的防范意识,任何事情都会小心谨慎,这其实是个好事,毕竟在互联网时代,越来越多的在线诈骗事件让人防不胜防。

小编前几天就看到一个关于如何打造安全可靠的短信验证码发送、查收的项目,觉得对于需要短信验证的各种项目是个不错的安全补充,特地赶紧放下享受假期来和大家分享~

在小编看来一个合规、安全、可靠的短信验证码项目模块应该具备以下几点特征:

  • 发送的验证码存在一定时间的有效期
  • 验证码不宜过长或过短
  • 同一手机号码不能频繁发送验证码请求
  • 验证码被使用后就失效

这个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(09));
            }

            return sbMsgCode.ToString();
        }
    }
}

十分清晰,就是运用随机函数生成满足长度要求的验证码,这里面长度是根据参数传入的,小编认为一般合理的验证码长度是6位,太短了容易被攻破,太长了的话使用起来对用户太不友好。看来6真是一个神奇的数字~

接着就是主要逻辑实现的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(10036, 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的大神开源提供的。

实话说,小编看了半天其中具体的实现逻辑,唔,没怎么看明白。。。因为里面用到了一些.net指针的方法,小编实在是。。。那说好听点嘛就是术业有专攻,但小编知道这种时候,我们就先用起来就行了!这里主要运用就是将生成图形验证码和手机号码绑定,从而达到在短信验证码请求信息中,进行正确的手机和对应图形验证码的校验:


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层完成了。小伙伴想额外增加其他校验的话也可以在这段逻辑里面自行增加,总的来说呢,这个项目逻辑清晰,即插即用,扩展性也不错,也非常适合想学习的小伙伴明白一个短信验证码从生成、发送、校验、生效通过这样一个完整的链路。

随着互联网的发展,光靠简单的密码密钥很难确保安全,短信验证想必会越来越普及,想学习了解的小伙伴,乘着假期赶紧来学习一波吧~项目完整地址如下:

点击下方卡片,关注公众号“TJ君

回复“短信验证”,获取仓库地址


往期推荐

往期推荐

渐变国旗微信头像来袭,不用你就OUT了!

深度学习spring boot必备的开源项目,国庆假期不来深造一番?

让你的收入节节攀高,一款开源家庭理财系统

报表能做到多好看?

Markdown编辑器功能不够多?自己加插件就行了!来看看这款开源Markdown编辑器





大家好,我是TJ

一个励志推荐10000款开源项目与工具的程序员

欢迎关注我,了解多好玩、有趣的科技资讯

: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存