Net Core Webapi 使用Redis实现连续登录失败N次 锁定账号N分钟

由于最近项目发现有尝试密码登录的操作,需要设置密码复杂度及账号多次登录失败,将账号锁定N分钟后,才可以继续登录操作。

开始思路是使用登录记录数据处理连续登录失败的问题,如果频繁请求可能会导致数据库查询变慢,影响数据库性能,但接口及查询功能实现了,mysql语句如下:

string sSql = "SELECT  COUNT(dlsfcg) AS dlsbCount FROM "
                        + " (SELECT id,dlsfcg,"
                        + " IF(@lastNum!=dlsfcg,@group:=@group+1,@group) AS g,"
                        + " IF(@lastNum!=dlsfcg,@lastNum:=dlsfcg,@lastNum) AS c  FROM "
                        + " (SELECT id,dlsfcg FROM rz_dlrz  WHERE dlsj>='"+ startTime + "' AND dlsj<='"+ endTime + "' AND yhm='"+ yhm + "') a,"
                        + " (SELECT @lastNum:=0,@group:=0 ) b ) m "
                        + " GROUP BY g  HAVING COUNT(1)>="+ tjcs + " ORDER BY id";

在数据库执行查询的数据看着是正常,可以查询出连续失败的记录,但是实际业务中未使用此接口及查询方法,如果实际业务中使用,请进行完整测试。

经过一番思考过后,突然想起项目中使用redis缓存,突发奇想地开始使用redis做文章,看看怎么实现缓存登录失败记录的问题,说干就干,于是开始编写代码。

appsettings.json配置:

"AccountLock": {
    "IsEnableLock": 1, //是否启用锁定 1-启用 其它值禁用
    "LockMinuties": 5, //锁定分钟数
    "LockNum": 5, //连续登录失败最大次数
    "CacheLockDataMinutes": 10, //缓存登录失败锁定数据分钟数
    "IsEnablePwdLength": 1, //是否启用密码长度 1-启用 其它值禁用
    "PasswordLength": 8//密码长度
  }

获取配置:

 /// <summary>
        /// 账号登录失败锁定配置
        /// </summary>
        public static class AccountLockSet
        {
            /// <summary>
            /// 是否启用锁定 1-启用 其它值禁用
            /// </summary>
            public static int IsEnableLock => Configuration["AccountLock:IsEnableLock"].ObjToInt();

            /// <summary>
            /// 锁定分钟数
            /// </summary>
            public static double LockMinuties => Configuration["AccountLock:LockMinuties"].ObjToMoney();

            /// <summary>
            /// 连续登录失败最大次数(锁定最大数)
            /// </summary>
            public static int LockNum => Configuration["AccountLock:LockNum"].ObjToInt();

            /// <summary>
            /// 缓存登录失败锁定数据分钟数
            /// </summary>
            public static int CacheLockDataMinutes => Configuration["AccountLock:CacheLockDataMinutes"].ObjToInt();

            /// <summary>
            /// 是否启用密码长度 1-启用 其它值禁用
            /// </summary>
            public static int IsEnablePwdLength => Configuration["AccountLock:IsEnablePwdLength"].ObjToInt();

            /// <summary>
            /// 密码长度
            /// </summary>
            public static int PasswordLength => Configuration["AccountLock:PasswordLength"].ObjToInt();
        }

上面我写成自定义的配置类,你可以根据自己的情况进行配置及读取配置文件内容。

编写方法实现:

        #region 登录失败大于指定次数锁定账号

        /// <summary>
        /// 登录失败大于指定次数锁定账号
        /// </summary>
        /// <param name="_sLockKey">redis key</param>
        /// <param name="_dLockMinuties">锁定时间 单位:分钟</param>
        /// <param name="_nLockNum">锁定次数</param>
        /// <param name="_nIsVerify">是否验证 1-验证锁定 默认-0 处理锁定数据</param>
        /// <param name="_nCacheLockDataMinutes">缓存锁定数据时间 单位:分钟,默认10分钟</param>
        /// <returns>Tuple item1:剩余次数,item2:锁定后解锁时间,item3:是否锁定 1-锁定 0-未锁定 </returns>
        [NonAction]
        public Tuple<int, double, int> fnAccountLock(string _sLockKey, double _dLockMinuties, int _nLockNum
            , int _nIsVerify = 0, int _nCacheLockDataMinutes = 10)
        {
            Tuple<int, DateTime> tupAccLock = null;

            if (this._cacheHelper.Exists(_sLockKey))
            {
                var vLockModel = this._cacheHelper.GetCache(_sLockKey);

                if (null != vLockModel)
                {
                    string sLockJson = JsonHelper.ToJson(vLockModel);

                    dynamic dyObj = JsonHelper.GetJSON<dynamic>(sLockJson);

                    int nLockNum = dyObj.Item1;
                    DateTime dtLockTime = dyObj.Item2;
                    double dLockMinutes = (dtLockTime - DateTime.Now).TotalMinutes;

                    if (nLockNum < _nLockNum)//缓存锁定次数小于指定锁定次数
                    {
                        int nResidueNum = _nLockNum - nLockNum;//剩余次数

                        if (_nIsVerify == 0)
                        {
                            nLockNum++;

                            nResidueNum = _nLockNum - nLockNum;//剩余次数

                            tupAccLock = new Tuple<int, DateTime>(nLockNum, DateTime.Now.AddMinutes(_dLockMinuties));

                            this._cacheHelper.Add(_sLockKey, tupAccLock, TimeSpan.FromMinutes(_nCacheLockDataMinutes));
                        }

                        if (nLockNum == _nLockNum)//缓存锁定次数等于指定锁定次数 返回锁定状态
                        {
                            return Tuple.Create(nResidueNum, dLockMinutes, 1);
                        }

                        return Tuple.Create(nResidueNum, dLockMinutes, 0);
                    }
                    else
                    {
                        if (dLockMinutes <= 0)
                        {
                            //小于指定锁定时间 删除锁定数据
                            this._cacheHelper.Remove(_sLockKey);

                            return Tuple.Create(0, dLockMinutes, 0);
                        }


                        return Tuple.Create(0, dLockMinutes, 1);
                    }
                }
                else
                {
                    if (_nIsVerify == 0)
                    {
                        tupAccLock = new Tuple<int, DateTime>(1, DateTime.Now.AddMinutes(_dLockMinuties));
                        this._cacheHelper.Add(_sLockKey, tupAccLock, TimeSpan.FromMinutes(_nCacheLockDataMinutes));

                        return Tuple.Create(4, _dLockMinuties, 0);
                    }

                    return Tuple.Create(_nLockNum, _dLockMinuties, 0);
                }
            }
            else
            {
                if (_nIsVerify == 0)
                {
                    tupAccLock = new Tuple<int, DateTime>(1, DateTime.Now.AddMinutes(_dLockMinuties));
                    this._cacheHelper.Add(_sLockKey, tupAccLock, TimeSpan.FromMinutes(_nCacheLockDataMinutes));

                    return Tuple.Create(4, _dLockMinuties, 0);
                }

                return Tuple.Create(_nLockNum, _dLockMinuties, 0);

            }
        }

        #endregion

由于此方法只在登录控制器使用,所有没有单独封装,直接写在了登录控制器内。

经测试,此方法貌似还没发现问题,如有问题可以留言给我,谢谢。

在登录接口中使用:

int IsEnablePwdLength = AccountLockSet.IsEnablePwdLength;
                int PasswordLength = AccountLockSet.PasswordLength;

                if (IsEnablePwdLength == 1 && model.mm?.Length < PasswordLength)
                {
                    return new MessageModel<object>()
                    {
                        code = 400,
                        success = false,
                        msg = "登录密码不符合要求!"
                    };
                }

                int IsEnableLock = AccountLockSet.IsEnableLock;
                double dLockMinuties = AccountLockSet.LockMinuties;
                int nLockNum = AccountLockSet.LockNum;
                int nCacheLockDataMinutes = AccountLockSet.CacheLockDataMinutes;

                if (IsEnableLock == 1)
                {
                    var vAccountLock = this.fnAccountLock(sLockKey, dLockMinuties, nLockNum, 1, nCacheLockDataMinutes);

                    if (vAccountLock.Item3 == 1)
                    {
                        return new MessageModel<object>()
                        {
                            code = 400,
                            success = false,
                            msg = "登录失败" + nLockNum + "次锁定" + dLockMinuties + "分钟,请在" + Math.Ceiling(vAccountLock.Item2) + "分钟后再试!"
                        };
                    }
                }

 登录失败提示:

if (null == vUserModel)
                {
                    yhdlrz.ms = "登录失败,用户名不存在";
                    await _logService.Add(yhdlrz);

                    var vAccountLock = this.fnAccountLock(sLockKey, dLockMinuties, nLockNum, _nCacheLockDataMinutes: nCacheLockDataMinutes);

                    string sMsgRet = "还有" + vAccountLock?.Item1 + "次机会!";

                    if (vAccountLock.Item1 <= 0)
                    {
                        sMsgRet = "当前账号已锁定,锁定" + dLockMinuties + "分钟!";
                    }

                    return new MessageModel<object>()
                    {
                        code = 400,
                        success = false,
                        msg = "登录失败,用户名不存在!" + sMsgRet
                    };
                }

vUserModel是根据用户名查询用户返回的对象,方法可在登录失败的地方多次调用,yhdlrz-记录登录日志,此代码你可以删除。

补充: 

//如果在锁定之前登录成功,则删除锁定数据
this._cacheHelper.Remove(sLockKey);

希望本文对你有帮助。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值