我们有一个 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 Framework 不会将我们的 HTTP 调用路由到路由。也就是说,如果我们收到一个 HTTP POST 请求,我们的应用程序甚至会在 20 多秒后才将该请求发送给控制器,但是,在同一时间段内,如果我们收到一个 HTTP GET 请求,我们的应用程序将立即处理该 GET !
目前我们有 300 个线程作为默认分叉池的 Akka 设置中的最小值/最大值。任何见解将不胜感激。我们运行一个运行 Ubuntu 的 t2.xlarge AWS EC2 实例。
谢谢。
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
}
}
}
希望这有帮助!
我一点也不喜欢这种做法。这会无缘无故地占用线程,如果有人发现您正在这样做并且他们有恶意的想法,可能会导致您的整个系统锁定。让我提出一个更好的方法:
在
User
表中存储上次登录尝试时间的可空LocalDateTime
。
当您从数据库中获取用户时,检查上次尝试时间(与 LocalDateTime.now() 比较),如果自上次尝试以来已过去 10 秒,则执行密码比较。
如果密码不匹配,请将上次尝试时间存储为现在。
如果您提供良好的错误响应,这也可以在前端优雅地处理。
编辑:如果您想延迟不基于用户的登录尝试,您可以创建一个尝试表并按 IP 地址存储上次尝试。
如果你真的想按照我不建议的方式去做,你需要先阅读这篇文章:https://www.playframework.com/documentation/2.8.x/ThreadPools
延迟响应确实惩罚了不小心输入错误密码的合法用户,我认为这不是一个好的应用程序设计。我在我的项目中做类似的事情,我在其中跟踪 ID 的失败计数,并在超过最大尝试次数时禁用 ID。在系统方面,我们将看起来可疑的 IP 列入黑名单。