要实现滑动 5 分钟窗口,您需要知道每次尝试的时间,而不仅仅是第一次或最后一次。
每次登录尝试都存储在数据库中。正如评论中提到的,仅使用会话缓存很容易绕过。
username | time
mr.mindor | 00:00:00
mr.mindor | 00:01:00
等等,然后对于每次登录尝试检查是否有 10 次或更少的尝试...
Select count(*)
from LOGIN_ATTEMPTS
where username = @current_user
AND time > DATEADD(m,-5,GetDate())
您可以出于安全/审计目的无限期保留 LOGIN_ATTEMPTS,也可以定期清除它,或者在成功登录的情况下。
仅存储第一次或最后一次和计数的问题。
如果没有每次,您将无法确定尝试的分布。
基于首次尝试的解决方案:
仅使用第一次尝试时间和计数,您可以在接下来的 5 分钟内阻止超过 10 次尝试,但在任何给定的 5 分钟间隔内不能阻止超过 10 次尝试。
示例:您可以在 0:00 获得 1 个,在 4:59 获得 9 个,并在 5:01 允许 10 个在几秒钟内获得 19 个。
attempt_time | record
0:00 | {0:00, 1}
4:59 | {0:00, 2}
4:59 | {0:00, 3}
4:59 | {0:00, 4}
4:59 | {0:00, 5}
4:59 | {0:00, 6}
4:59 | {0:00, 7}
4:59 | {0:00, 8}
4:59 | {0:00, 9}
4:59 | {0:00, 10}
5:01 | {5:00, 1} // 10 since 4:59
5:02 | {5:00, 2} // 11 since 4:59
基于最后一次尝试的解决方案:
只保存最后一次尝试时间和一个计数,您可以在任何 5 分钟间隔内防止超过 10 次尝试,但实际上限制性更大。它会阻止超过 10 次尝试,每次尝试都在前一次尝试的 5 分钟内。
示例:如果每 4 分钟尝试一次,您的计数器将永远不会重置。
attempt_time | record
0:00 | {0:00, 1}
4:00 | {4:00, 2} // 2 in last 5 minutes
8:00 | {8:00, 3} // 2 in last 5 minutes
...
40:00 | {40:00,11} // blocked but only 2 in last 5 minutes.