4

为了防止密码暴力破解,并防止试图检测有效用户名的计时攻击,我希望我的登录过程花费恒定的时间,无论身份验证是否成功。简而言之,无论结果如何,我总是希望我的登录过程花费 1000 毫秒(假设任一结果都需要一小部分时间)。在 Java 中完成此任务的最佳方法是什么?这是我目前的想法:

class ConstantDuration<T> implements Callable<T> {

 ConstantDuration(Callable<T> task,long duration) {
  this.task = task;
  this.duration = duration;
 }

 public T call() throws Exception {
  long start = System.currentTimeMillis();
  long elapsed = 0;
  T result = null;
  try {
   result = task.call();
  } finally {
    elapsed = System.currentTimeMillis() - start;
  }
  if ( elapsed < duration ) {
   Thread.sleep(duration-elapsed);
  }
  return result;
 }
}
4

4 回答 4

2

显然,您需要将 sleep 移到 finally 块中,因此如果任务抛出异常(例如 PasswordExpiredException?),也会发生这种情况。

另一个问题是办案elapsed > duration。你说这永远不会发生,但你确定吗?如果您的数据库查询被锁停止了怎么办?假设您想在这种情况下拒绝身份验证,您可以这样做:

ExecutorService exec = Executors.newFixedThreadPool(10);

<T> T doInConstantTime(Callable<T> task, long millis, T defaultResponse) {
    Future<T> future = exec.submit(task);
    Thread.sleep(millis);
    if (future.isDone()) {
        return future.get();
    } else {
        future.cancel(false); // or true? 
        return defaultResponse;
    }
}

(当然,您需要添加适当的异常处理)

不过,我不确定这是防御蛮力攻击的好方法。相反,我们会在第三次连续登录尝试失败后(一段时间或直到管理员解锁帐户)使用户的登录无效。在某处提到你这样做,没有人有理由暴力破解密码。

于 2012-02-11T14:29:12.533 回答
2

如果您想让每次登录尝试花费一些最短时间,同时希望能够处理大量用户(例如每秒 1000 次登录),则无法使用简单的“让线程等待一些时间”模型 - 基本上,你是在自己做 DOS,因为在 Java 中,每个线程都会占用一些资源,因此线程的总数是有限的,即使它们实际上并没有做任何事情。

为了保持可扩展性,您应该使用异步 I/O 来获取用户的登录请求、检查密码(也可能是异步的)、设置何时重新访问该用户的连接的计时器,然后回复(也是异步的)。不幸的是,Java 使编写(然后读取)变得相当复杂。

于 2012-02-11T17:00:56.883 回答
0

你将如何切断对 result = task.call(); 的调用 如果超过1000ms?您所需要的只是一个在 t1 时间之前不返回的函数,无论执行是否完成。一个简单的解决方案是,在将任务提交给线程池执行程序后,在执行线程中使用睡眠或等待。然后检查future.isDone()是否。如果不取消,如果是,则您已经等待了 1000 毫秒。

于 2012-02-11T13:38:02.623 回答
0

在你的线程中休眠意味着线程将无法做任何事情。那不会扩展。

使用计划的执行器并计划一个延迟的任务,该任务将在未来返回结果。 http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/ScheduledExecutorService.html

于 2012-02-11T13:48:29.483 回答