【问题标题】:Play Framework how to purposely delay a responsePlay Framework 如何故意延迟响应
【发布时间】:2020-02-27 01:34:12
【问题描述】:

我们有一个 Play 应用,目前使用的是 2.6 版。我们试图通过在用户提供失败密码时延迟向用户发送“登录失败”消息来防止字典攻击我们的登录。我们目前散列和加盐并拥有所有最佳实践,但我们不确定我们是否正确延迟。所以我们在控制器中有:

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 框架不会将我们的 HTTP 调用路由到 Routes。也就是说,如果我们收到一个 HTTP POST 请求,我们的应用程序甚至不会将该请求发送到控制器,直到 20 多秒后,但是,如果我们收到一个 HTTP GET 请求,在同一时间段内,我们的应用程序将立即处理该 GET !

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

谢谢。

【问题讨论】:

    标签: amazon-web-services playframework threadpool


    【解决方案1】:

    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
        }
      }
    }
    

    希望这会有所帮助!

    【讨论】:

    • 哇,太棒了!谢谢伊万——你是一个救生员!这是网络上唯一展示此类实际示例的地方!
    【解决方案2】:

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

    User 表中存储最后一次登录尝试时间的可为空的LocalDateTime

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

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

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

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

    如果您真的想按照自己的方式行事,我不建议您先阅读以下内容:https://www.playframework.com/documentation/2.8.x/ThreadPools

    【讨论】:

    • 关于时间延迟检查的想法很棒——很有意义。让我们假设挑战是将控制器的响应延迟 10 秒。我已经阅读了所有关于线程池的信息。控制器是否会响应一个未来,而那个未来会有一个在 10 秒后停止的无限循环?对于 10 秒延迟不会使 CPU 达到峰值,没有参考实现。
    • 那么您可能想要使用自定义调度程序并且只对其进行延迟。与他们在此处的操作类似github.com/playframework/play-samples/blob/2.8.x/… 请注意如何为数据库操作提供执行上下文。我再次强烈建议不要使用这种模式。
    猜你喜欢
    • 2018-01-02
    • 1970-01-01
    • 1970-01-01
    • 2023-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多