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 static java.util.Optional.empty;
|
||||||
|
|
||||||
import de.srsoftware.oidc.api.data.AccessToken;
|
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 de.srsoftware.oidc.api.data.User;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public interface UserService {
|
public interface UserService {
|
||||||
Map<String, FailedLogin> failedLogins = new HashMap<>();
|
Map<String, Lock> failedLogins = new HashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create a new access token for a given user
|
* create a new access token for a given user
|
||||||
@@ -30,25 +30,20 @@ public interface UserService {
|
|||||||
public UserService init(User defaultUser);
|
public UserService init(User defaultUser);
|
||||||
public List<User> list();
|
public List<User> list();
|
||||||
public Set<User> find(String idOrEmail);
|
public Set<User> find(String idOrEmail);
|
||||||
public default Optional<FailedLogin> getLock(String id) {
|
public default Optional<Lock> getLock(String key) {
|
||||||
var failedLogin = failedLogins.get(id);
|
var failedLogin = failedLogins.get(key);
|
||||||
if (failedLogin == null || failedLogin.releaseTime().isBefore(Instant.now())) return empty();
|
if (failedLogin == null || failedLogin.releaseTime().isBefore(Instant.now())) return empty();
|
||||||
return Optional.of(failedLogin);
|
return Optional.of(failedLogin);
|
||||||
}
|
}
|
||||||
public Optional<User> load(String id);
|
public Optional<User> load(String id);
|
||||||
public Optional<User> login(String username, String password);
|
public Optional<User> login(String username, String password);
|
||||||
public default UserService lock(String id) {
|
public default Lock lock(String key) {
|
||||||
var failedLogin = failedLogins.get(id);
|
return failedLogins.computeIfAbsent(key,k -> new Lock()).count();
|
||||||
if (failedLogin == null) {
|
|
||||||
failedLogins.put(id, failedLogin = new FailedLogin(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
public boolean passwordMatches(String plaintextPassword, User user);
|
public boolean passwordMatches(String plaintextPassword, User user);
|
||||||
public UserService save(User user);
|
public UserService save(User user);
|
||||||
public default UserService unlock(String id) {
|
public default UserService unlock(String key) {
|
||||||
failedLogins.remove(id);
|
failedLogins.remove(key);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public UserService updatePassword(User user, String plaintextPassword);
|
public UserService updatePassword(User user, String plaintextPassword);
|
||||||
|
|||||||
@@ -3,23 +3,21 @@ package de.srsoftware.oidc.api.data;
|
|||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
public class FailedLogin {
|
public class Lock {
|
||||||
private final String userId;
|
|
||||||
private int attempts;
|
private int attempts;
|
||||||
private Instant releaseTime;
|
private Instant releaseTime;
|
||||||
|
|
||||||
public FailedLogin(String userId) {
|
public Lock() {
|
||||||
this.userId = userId;
|
|
||||||
this.attempts = 0;
|
this.attempts = 0;
|
||||||
count();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void count() {
|
public Lock count() {
|
||||||
attempts++;
|
attempts++;
|
||||||
if (attempts > 13) attempts = 13;
|
if (attempts > 13) attempts = 13;
|
||||||
var seconds = 1;
|
var seconds = 5;
|
||||||
for (long i = 0; i < attempts; i++) seconds *= 2;
|
for (long i = 0; i < attempts; i++) seconds *= 2;
|
||||||
releaseTime = Instant.now().plusSeconds(seconds);
|
releaseTime = Instant.now().plusSeconds(seconds);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int attempts() {
|
public int attempts() {
|
||||||
@@ -98,7 +98,7 @@ public class EncryptedUserService extends EncryptedConfig implements UserService
|
|||||||
var optLock = getLock(username);
|
var optLock = getLock(username);
|
||||||
if (optLock.isPresent()) {
|
if (optLock.isPresent()) {
|
||||||
var lock = optLock.get();
|
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();
|
return empty();
|
||||||
}
|
}
|
||||||
for (var encryptedUser : backend.list()) {
|
for (var encryptedUser : backend.list()) {
|
||||||
@@ -109,7 +109,9 @@ public class EncryptedUserService extends EncryptedConfig implements UserService
|
|||||||
return Optional.of(decryptedUser);
|
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();
|
return empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user