实现思路

0.在请求登录接口时判断是否登录成功,
1.如果登录失败将记录当前的用户登录次数其有效期为一分钟,一分钟后重新计次,
查找在用户10分钟内的登录记录,超过设定的次数后禁止登录,
在规定时间内登录成功,取消所有计次,并解锁用户
2.如果登录成功,取消所有计次,并解锁用户登录
3.实现仅依赖于系统缓存服务除此无任何依赖

代码

#region AuthorInfo

// Author: sjx

// Date: 2023-08-10 16:22

#endregion


using Microsoft.AspNetCore.Authorization;

using Microsoft.AspNetCore.Mvc;

using Microsoft.Extensions.Caching.Memory;


namespace Api_Limit.Controllers;


[ApiController]

[Route("[controller]/[action]")]

public class UserAuthorController : ControllerBase

{

    #region Ctor


    public UserAuthorController(IMemoryCache memoryCache)

    {

        // 注入缓存,并设置缓存

        LimitUserLogin.MemoryCache = memoryCache;

    }


    #endregion


    #region Action


    #region 登录


    [AllowAnonymous]

    [HttpPost(Name = "UserLogin")]

    public string UserLogin(UserLogInfo input)

    {


        LimitUserLogin.FailCount = 5;

        LimitUserLogin.LockTime = 30;


        // 模拟用户数据

        var userList = new List<UserLogInfo>()

        {

            new UserLogInfo()

            {

                Account = "admin",

                Password = "123456"

            },

            new UserLogInfo()

            {

                Account = "user",

                Password = "123456"

            },

            new UserLogInfo()

            {

                Account = "test",

                Password = "123456"

            }

        };


        // 判断是否被锁定

        var isLock = LimitUserLogin.IsForbidden(input.Account);

        if (isLock) return "账号已被锁定,请30分钟后再试";


        // 验证账号密码是否正确

        var user = userList.FirstOrDefault(x => x.Account == input.Account && x.Password == input.Password);

        var isOk = user != null;

        if (!isOk)

        {

            // 登录失败,计次

            LimitUserLogin.SetFailCounter(input.Account);

            return "账号或密码错误";

        }


        // 登录成功,解锁

        LimitUserLogin.UnLock(input.Account);

        return "登录成功";

    }


    #endregion


    #endregion

}


/// <summary>

/// 模拟用户数据

/// </summary>

public class UserLogInfo

{

    public string Account { get; set; } = string.Empty;


    public string Password { get; set; } = string.Empty;

}


/// <summary>

/// 用户登录限制

/// </summary>

public static class LimitUserLogin

{

    #region Field


    private const string FailCountRedisKey = "login_fail_count";

    private const string Separator = ":";


    /// <summary>

    /// 系统缓存

    /// </summary>

    public static IMemoryCache MemoryCache = null!;


    /// <summary>

    /// 锁定时间

    /// </summary>

    public static int LockTime = 30;


    /// <summary>

    /// 失败次数

    /// </summary>

    public static int FailCount = 5;


    #endregion


    #region Methods


    #region 是否被锁定


    /// <summary>

    /// 是否被锁定

    /// </summary>

    /// <param name="account">用户名</param>

    /// <returns></returns>

    public static bool IsForbidden(string account)

    {

        CheckCache();

        try

        {

            // 判断是否被锁定

            return MemoryCache.TryGetValue(string.Join(Separator, FailCountRedisKey, account), out int failCount);

        }

        catch (Exception e)

        {

            Console.WriteLine(e);

        }


        return false;

    }


    #endregion


    #region 登录失败计次


    /// <summary>

    /// 失败计次

    /// </summary>

    /// <param name="username">用户名</param>

    public static void SetFailCounter(string username)

    {

        CheckCache();

        // 获取当前时间

        var now = DateTime.Now;

        // 获取当前时间的分钟数

        var minute = now.ToString("yyyyMMddHHmm");


        // 登录失败次数 + 1

        var key = string.Join(Separator, FailCountRedisKey, username, minute);

        var count = MemoryCache.Get<int?>(key) ?? 0;


        // 如果key不存在的话就会以增量形式存储进来

        MemoryCache.Set(key, count + 1, TimeSpan.FromMinutes(1));


        // 如果失败次数大于5次,锁定账户

        var windowsKeys = new List<string>();

        // 获取当前时间前10分钟的key

        for (var i = 0; i < 10; i++)

        {

            windowsKeys.Add(string.Join(Separator, FailCountRedisKey, username, now.ToString("yyyyMMddHHmm")));

            now = now.AddMinutes(-1);

        }


        var countList = new List<int>();

        // 获取当前时间前10分钟的失败次数

        foreach (var key1 in windowsKeys)

        {

            if (MemoryCache.TryGetValue(key1, out int count1))

            {

                countList.Add(count1);

            }

        }


        // 获取当前时间前10分钟的失败次数总和

        var total = countList.Sum();


        // 如果失败次数大于5次,锁定账户

        if (total >= FailCount)

        {

            Forbidden(username);

        }

    }


    #endregion


    #region 禁止登录


    /// <summary>

    /// 禁止登录30分钟

    /// </summary>

    /// <param name="account">用户名</param>

    private static void Forbidden(string account)

    {

        CheckCache();

        // 设置缓存

        var key = string.Join(Separator, FailCountRedisKey, account);

        // 设置30分钟过期

        MemoryCache.Set(key, 1, TimeSpan.FromMinutes(LockTime));

    }


    #endregion


    #region 解锁


    /// <summary>

    /// 解锁

    /// </summary>

    /// <param name="account">用户名</param>

    public static void UnLock(string account)

    {

        CheckCache();

        // 获取当前时间的分钟数

        var minute = DateTime.Now.Minute;

        var windowsKey = new List<string>();

        for (var i = 0; i <= 10; i++)

        {

            windowsKey.Add(string.Join(Separator, FailCountRedisKey, account, DateTime.Now.Minute - i));

        }


        // 删除缓存

        foreach (var key in windowsKey)

        {

            MemoryCache.Remove(key);

        }

    }


    #endregion


    #region 检查缓存是否存在


    private static void CheckCache()

    {

        if (MemoryCache == null) throw new Exception("请先初始化MemoryCache");

    }


    #endregion


    #endregion

}
最后修改:2023 年 08 月 15 日
如果觉得我的文章对你有用,请随意赞赏