Play Framework 如何故意延迟响应

Play Framework how to purposely delay a response

我们有一个 Play 应用程序,目前使用的是 2.6 版。我们正试图通过在用户提供密码失败时延迟 "failed login" 消息返回给我们的用户来防止对我们登录的字典攻击。我们目前哈希和加盐并拥有所有最佳实践,但我们不确定我们是否正确延迟。所以我们在控制器中有:

public Result login() { return ok(loginHtml) }

我们有一个:

public Result loginAction()
{ 
  // Check for user in database
  User user = User.find.query()...

  // Was the user found?
  if (user == null) {

     // Wrong password! Delay and redirect
     Thread.sleep(10000);  <<-- how do delay correctly?
     return redirect(routes.Controller.login())
  }

  // User is not null, so all good!
  ...

}

我们不确定 Thread.sleep(10000) 是否是延迟响应的最佳方式,因为这可能会挂起其他传入的请求,或者使用默认池中的过多线程。我们注意到,每秒点击次数低于 80 次以上时,Play Framework 不会将我们的 HTTP 调用路由到路由。也就是说,如果我们收到一个 HTTP POST 请求,我们的应用程序甚至会在 20 多秒后才将该请求发送到控制器,但是,如果我们收到一个 HTTP GET 请求,在同一时间段内,我们的应用程序将立即获取的过程!

目前我们的 Akka 设置中有 300 个线程作为默认分叉池的 min/max。任何见解将不胜感激。我们 运行 一个 t2.xlarge AWS EC2 实例 运行ning Ubuntu.

谢谢。

我完全不喜欢这种方法。这会无缘无故地占用线程,如果有人发现您正在这样做并且他们有恶意的想法,可能会导致您的整个系统锁定。让我提出一个更好的方法:

User table 中存储上次登录尝试时间的可为空 LocalDateTime

当您从数据库中获取用户时,检查上次尝试时间(与 LocalDateTime.now() 比较),如果自上次尝试以来已过去 10 秒,则执行密码比较。

如果密码不匹配,则将上次尝试时间存储为现在。

如果您提供良好的错误响应,这也可以在前端优雅地处理。

编辑:如果您想延迟不基于用户的登录尝试,您可以创建一个尝试 table 并按 IP 地址存储上次尝试。

如果你真的想按照我不推荐的方式去做,你需要先阅读这篇文章:https://www.playframework.com/documentation/2.8.x/ThreadPools

Thread.sleep导致当前线程阻塞,请尽量避免在生产代码中使用。

您需要使用的是 CompletionStage / CompletableFuture 或任何用于处理异步编程和异步操作的抽象。

请查看有关异步操作的更多详细信息:https://www.playframework.com/documentation/2.8.x/JavaAsync

在你的情况下,解决方案看起来也很像(打扰一下,这可能有错误 - 我是 Scala 初级工程师):

import play.libs.concurrent.HttpExecutionContext;
import play.mvc.*;

import javax.inject.Inject;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

public class LoginController extends Controller {

  private HttpExecutionContext httpExecutionContext;

  // Create and inject separate ScheduledExecutorService
  private ScheduledExecutorService executor; 

  @Inject
  public LoginController(HttpExecutionContext ec,
                         ScheduledExecutorService executor) {
    this.httpExecutionContext = ec;
    this.executor = executor;
  }

  public CompletionStage<Result> loginAction() {
    User user = User.find.query()...
    if (user == null) {
        return executor.schedule(() -> {redirect(routes.Controller.login());}, 10, TimeUnit.SECONDS);
    } else {
      // return another response
    }
  }
}

希望对您有所帮助!