implemented brute force protection
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -4,13 +4,13 @@ package de.srsoftware.oidc.api;
|
||||
import static java.util.Optional.empty;
|
||||
|
||||
import de.srsoftware.oidc.api.data.AccessToken;
|
||||
import de.srsoftware.oidc.api.data.FailedLogin;
|
||||
import de.srsoftware.oidc.api.data.Lock;
|
||||
import de.srsoftware.oidc.api.data.User;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
|
||||
public interface UserService {
|
||||
Map<String, FailedLogin> failedLogins = new HashMap<>();
|
||||
Map<String, Lock> failedLogins = new HashMap<>();
|
||||
|
||||
/**
|
||||
* create a new access token for a given user
|
||||
@@ -30,25 +30,20 @@ public interface UserService {
|
||||
public UserService init(User defaultUser);
|
||||
public List<User> list();
|
||||
public Set<User> find(String idOrEmail);
|
||||
public default Optional<FailedLogin> getLock(String id) {
|
||||
var failedLogin = failedLogins.get(id);
|
||||
public default Optional<Lock> getLock(String key) {
|
||||
var failedLogin = failedLogins.get(key);
|
||||
if (failedLogin == null || failedLogin.releaseTime().isBefore(Instant.now())) return empty();
|
||||
return Optional.of(failedLogin);
|
||||
}
|
||||
public Optional<User> load(String id);
|
||||
public Optional<User> login(String username, String password);
|
||||
public default UserService lock(String id) {
|
||||
var failedLogin = failedLogins.get(id);
|
||||
if (failedLogin == null) {
|
||||
failedLogins.put(id, failedLogin = new FailedLogin(id));
|
||||
}
|
||||
|
||||
return this;
|
||||
public default Lock lock(String key) {
|
||||
return failedLogins.computeIfAbsent(key,k -> new Lock()).count();
|
||||
}
|
||||
public boolean passwordMatches(String plaintextPassword, User user);
|
||||
public UserService save(User user);
|
||||
public default UserService unlock(String id) {
|
||||
failedLogins.remove(id);
|
||||
public default UserService unlock(String key) {
|
||||
failedLogins.remove(key);
|
||||
return this;
|
||||
}
|
||||
public UserService updatePassword(User user, String plaintextPassword);
|
||||
|
||||
@@ -3,23 +3,21 @@ package de.srsoftware.oidc.api.data;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public class FailedLogin {
|
||||
private final String userId;
|
||||
public class Lock {
|
||||
private int attempts;
|
||||
private Instant releaseTime;
|
||||
|
||||
public FailedLogin(String userId) {
|
||||
this.userId = userId;
|
||||
public Lock() {
|
||||
this.attempts = 0;
|
||||
count();
|
||||
}
|
||||
|
||||
public void count() {
|
||||
public Lock count() {
|
||||
attempts++;
|
||||
if (attempts > 13) attempts = 13;
|
||||
var seconds = 1;
|
||||
var seconds = 5;
|
||||
for (long i = 0; i < attempts; i++) seconds *= 2;
|
||||
releaseTime = Instant.now().plusSeconds(seconds);
|
||||
return this;
|
||||
}
|
||||
|
||||
public int attempts() {
|
||||
@@ -98,7 +98,7 @@ public class EncryptedUserService extends EncryptedConfig implements UserService
|
||||
var optLock = getLock(username);
|
||||
if (optLock.isPresent()) {
|
||||
var lock = optLock.get();
|
||||
LOG.log(WARNING, "{} is locked after {} failed logins. Lock will be released at {}", username, lock.attempts(), lock.releaseTime());
|
||||
LOG.log(WARNING, "{0} is locked after {1} failed logins. Lock will be released at {2}", username, lock.attempts(), lock.releaseTime());
|
||||
return empty();
|
||||
}
|
||||
for (var encryptedUser : backend.list()) {
|
||||
@@ -109,7 +109,9 @@ public class EncryptedUserService extends EncryptedConfig implements UserService
|
||||
return Optional.of(decryptedUser);
|
||||
}
|
||||
}
|
||||
lock(username);
|
||||
|
||||
var lock = lock(username);
|
||||
LOG.log(WARNING,"Login failed for {0} → locking account until {1}",username,lock.releaseTime());
|
||||
return empty();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user