6

我正在尝试解决这个业务问题:

  • 用户每 5 分钟尝试登录 10 次
  • 如果用户尝试超过 10 次,我会显示“您需要等待登录”消息
  • 一旦 5 分钟过去了,我需要重置尝试次数并让用户再尝试 10 次。

我想在不使用 a 的情况下执行此操作,Timer并且我将大部分信息存储在Session

public class LoginExp
{
  public DateTime FirstAttempt;
  public int NumOfAttempts;
}

我将FirstAttempt日期时间存储在页面加载上。

在登录按钮上单击:

  • 我增加NumOfAttempts
  • 我需要检查 NumOfAttempts同一 5 分钟时间段内是否 < 10。我可以得到和之间经过的分钟数,FirstAttempt然后DateTime.Now做一个 5 的 mod,但这不会告诉我这是在哪个时间段(换句话说,用户可能在第 2 分钟尝试了 3 次然后回来在第 7 分钟再次尝试。这mod会给我相同的价值。

关于我如何做到这一点的任何想法?

4

5 回答 5

6

请不要使用计时器。为这些数据与用户数据腾出空间,以便持久化,存储某种形式的凭据。只需保留上次登录尝试时间和尝试次数 - 如果时间大于n,则下一次尝试将清除先前的计数并从 0 开始,依此类推。

于 2012-11-02T16:55:40.460 回答
2

您可以存储尝试时间戳列表并使用它来确定用户是否被锁定。这不是完整的代码,但这是基本思想:

// Store a list of attempts
var attempts = new List<DateTime>();

// Determine if 10 or more attempts have been made in the last 5 minutes
bool lockout = attempts.Where(a => (DateTime.Now - a).TotalMinutes <= 5).Count() >= 10;

// Register an attempt
attempts.Add(DateTime.Now);

// Remove attempts older than 5 minutes
attempts.Where(a => (DateTime.Now - a).TotalMinutes > 5).ToList()
    .ForEach(a => attempts.Remove(a));

您还可以将尝试存储在数据库中,这将为您提供用于安全目的的记录。同样的方法也适用——计算不到 5 分钟的记录。

于 2012-11-02T17:02:35.620 回答
1

答案取决于您如何解释需求​​。也就是说,5 分钟的时间框架是一个滑动尺度吗?

该要求的严格应用将要求您为登录尝试创建一个数据库表,其中每条记录都包括用户记录的密钥和登录尝试的时间戳。然后对于每次尝试,执行一个查询,如

delete from login_attempts where user=[user] and attempt_time<[now - 5 minutes]
select count(*) from login_attempts where user=[user]

(括号中的值被替换为合适的值,无论您使用准备好的语句还是其他)

然后,如果返回的计数 > 10,则阻止该尝试。

(我建议每次都删除旧的尝试以保持表格清洁。当然,可以使用其他方法。)

如果不需要那么严格地应用要求并且您不想创建新表,则可以通过例如将上次尝试时间和计数添加到用户记录来简化它。然后在每次尝试时(这里是伪代码......):

read last_attempt and attempt_count
if last_attempt < now - 5 minutes then
  attempt_count=0
endif
if attempt_count>10 then
  display error
else if attempt_fails then
  last_attempt=now
  attempt_count=attempt_count+1
endif

这并不严格满足规定的要求:如果用户每 4 分钟尝试一次,那么 40 分钟后我们会说他有 10 次错误尝试,而实际上除了其中一次之外,所有尝试都超过 5 分钟。但它会比创建一个新表更简单,并且会实现我认为的目标,阻止某人通过暴力破解密码。

于 2012-11-02T17:16:51.040 回答
0

要实现滑动 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.
于 2012-11-02T17:12:29.233 回答
0

计时器在这里很丑。只需保留某种尝试表即可。存储 UserId、尝试时间和成功/失败标志。

从那里逻辑是不言自明的。

于 2012-11-02T17:13:30.033 回答