<返回更多

c#实现简单Token口令验证

2020-07-22    
加入收藏

最近一个项目发现手机验证码总是被人盗刷,一秒钟刷了1百多个,很明显这种行为是通过软件自动提交的,自动发帖机原理类似,解决这个问题目前有两个方案。

出现这个问题原因:请求手机验证码Api时没有任何带任何验证,只要请求了手机号正确就执行发送操作,软件或代码很容易伪造请求过程。

解决方案有很多种,可以选择下面一种或几种组合起来使用。

方案1:用户获取手机验证码时候弹出图片验证码,输入后再发送。

优点:增加伪造请求成功的难度,必须输入验证码才可以发送,如果是软件,软件需要有图片验证码识别功能。

缺点:用户体验不好,增加了普通用户的操作步骤。

方案2:增加ip黑名单,即检测请求ip的发送频率,如同一个ip一分钟内请求多少次后屏蔽ip。

缺点:一些软件有主动更换ip的功能,这种方式效果不是很好。

方案3:加上token口令验证

在Api中增加口令验证,即每次请求必须带上token,token验证正确了才执行发送操作。

这个方案为了替代方案1,因为有的公司对前端体验要求极高,增加用户操作步骤后用户体验不好。

这里token可以放在数据库中,也可以放在内存中,个人建议放在内存中,速度快,查询快,至于冗余的问题,可以增加一个BuildDate来保存生成时间。

Token描述类

   public class TokenDescriptor
    {
        /// <summary>
        /// 客户端唯一Id,必须保证唯一性
        /// </summary>
        public string ClientId { get; set; }
        /// <summary>
        /// token
        /// </summary>

        public string Token { get; set; }

        /// <summary>
        /// token生成日期
        /// </summary>
        public DateTime BuildDate { get; set; }
    }

token处理类

  public class TokenFactoryBLL
    {
        private string _ClientId;
        private static List<TokenDescriptor> _TokenList;

        static TokenFactoryBLL()
        {
            _TokenList = new List<TokenDescriptor>();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="httpRequestBase"></param>
        public TokenFactoryBLL(HttpRequestBase httpRequestBase)
        {
            _ClientId = StringHelper.ClientId(httpRequestBase);
            ClearExpired();
        }

        /// <summary>
        /// 可用于远程api
        /// </summary>
        /// <param name="clientId"></param>
        public TokenFactoryBLL(string clientId)
        {
            _ClientId = clientId;
            ClearExpired();
        }

        /// <summary>
        /// 生成口令
        /// </summary>
        public string Get()
        {
            if (string.IsNullOrEmpty(_ClientId))
            {
                return null;
            }
            string token = Guid.NewGuid().ToString("N");//guid;
            TokenDescriptor tokenDescriptor = new TokenDescriptor();
            tokenDescriptor.ClientId = _ClientId;
            tokenDescriptor.Token = StringHelper.NewGuid();
            tokenDescriptor.BuildDate = Convert.ToDateTime(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
            _TokenList.Add(tokenDescriptor);
            return token;
        }

        /// <summary>
        /// 验证token
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        public TipsInfo Validate(string token)
        {
            TipsInfo tipsInfo = new TipsInfo();
            if (!_TokenList.Exists(c => c.ClientId.Equals(_ClientId, StringComparison.OrdinalIgnoreCase) && c.Token.Equals(token, StringComparison.OrdinalIgnoreCase)))
            {
                tipsInfo.State = 0;
                tipsInfo.Msg = "token口令验证失败";
            }
            return tipsInfo;
        }

        /// <summary>
        /// 移除对应客户端的所有token
        /// </summary>
        /// <param name="clientId"></param>
        public void Remove()
        {
            _TokenList.RemoveAll(c => c.ClientId.Equals(_ClientId, StringComparison.OrdinalIgnoreCase));
        }


        /// <summary>
        /// 清理过期的token,过期时间10分钟
        /// </summary>
        private static void ClearExpired()
        {
            for (var i = 0; i < _TokenList.Count; i++)
            {
                TokenDescriptor item = _TokenList[i];
                DateTime startTime = item.BuildDate;
                DateTime endTime = Convert.ToDateTime(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
                TimeSpan ts = endTime - startTime;
                int minutes = ts.Minutes;
                if (minutes>10)
                {
                    _TokenList.RemoveAt(i);
                }
            }
        }
    }

附上Helper工具类中clientId的方法:

        /// <summary>
        /// 获取并生成客户端唯一Id,保存在cookie中;
        /// </summary>
        /// <returns></returns>
        public static string ClientId(HttpRequestBase request) //获取客户端唯一Id
        {
            if (request == null)
            {
                return null;
            }
            string clientId = CookieHelper.Get("_clientId_");
            if (string.IsNullOrEmpty(clientId))
            {
                string guid = Guid.NewGuid().ToString("N");//guid
                string ip = StringHelper.GetClientIP().ToString().Replace(".", "").Replace(":", "");
                clientId = guid + ip;
                CookieHelper.Add("_clientId_", clientId);
            }
            return clientId;
        }

        /// <summary>
        /// 获取客户端ip
        /// </summary>
        /// <returns></returns>
        public static string GetClientIP()
        {
            if (System.Web.HttpContext.Current == null) return "127.0.0.1";
            string clientIp = System.Web.HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
            if (string.IsNullOrEmpty(clientIp))
            {
                clientIp = System.Web.HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
            }
            if (string.IsNullOrEmpty(clientIp))
            {
                clientIp = System.Web.HttpContext.Current.Request.UserHostAddress;
            }
            if (clientIp.IndexOf(",") > 0)
            {
                clientIp = clientIp.Split(',')[0];
            }
            if (!StringHelper.IsIp(clientIp))
            {
                clientIp = "127.0.0.1";
            }
            return clientIp;
        }

前端发送方式需要更改,在请求手机验证码的api之前,先第一次请求获取token,然后带上token验证通过后后再请求api。

这个方案肯定没有验证码的防护效果好,只是增加了伪造请求的步骤,因为现在很多模拟请求的软件都是一次性请求,所以还是能防护大部分的软件。

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍最近更新
更多资讯 >>>