I have an application written with Java, Spring, Hibernate, Postgres.
It tracks user's login attempts. If user has more than 5 invalid attempts from the same IP address in less than 1 hour - this IP address will be blocked .
I have the following entity class for storing information about login attempts:
@Entity
public class BlockedIp {
private String ipAddress;
private Date blockDate;
private Date unblockDate;
private Date lastAttemptDate;
private Integer wrongAttemptsCount;
...}
First, when app gets login request - it checks if IP address is already blocked (blockDate != null). Then in returns special response code to user.
If app gets login request and credentials are wrong:
- if last attempt was less than 1 hour ago - it increments wrongAttemptsCount and if (wrongAttemptsCount == 5) - sets blockDate.
- if last attempt was more than 1 hour ago - it sets wrongAttemptsCount to 1.
If app gets login request and credentials are correct - it resets wrongAttemptsCount to zero, so user can make up to 5 mistakes again :)
The issue is when some users try to login simultaneously from the same IP. For instance, wrongAttemptsCount = 4, so user can have only one last attempt. And we have 3 incoming requests, and they all are with wrong credentials. In theory, only first request will pass, but two others will be blocked. In practice, of course, the all get wrongAttemptsCount equals to 4 from database, so they all will be processed as non-blocked.
So, which options i have to solve this issue and with minimal performance loss, if possible? I thought about SELECT FOR UPDATE statement for my query but my colleague said that this will seriously hit performance. Also he offered to look at @Version annotation. Is it really worth it? Is it much faster? Maybe someone can offer other options?