Class DefaultLockingBruteForceProtector
- All Implemented Interfaces:
Provider,BruteForceProtector
Semaphore.
The database row for login failures is only locked with PESSIMISTIC_WRITE when a write occurs
(see UserLoginFailureAdapter.ensureLocked()), not on reads.
Without additional coordination, parallel login attempts could all read the same (stale) failure count,
allowing more attempts than the configured limit. The JVM-level lock prevents this by ensuring only one
thread per user per node reads and updates the failure state at a time.
This is a performance trade-off: acquiring a database lock on every read (including
DefaultBruteForceProtector.isTemporarilyDisabled(org.keycloak.models.KeycloakSession, org.keycloak.models.RealmModel, org.keycloak.models.UserModel) checks on every login) would be expensive. The JVM-level lock avoids that
cost while still providing correctness within a single node. In a cluster, the upper bound for concurrent
attempts that may bypass the check equals the number of nodes, as each node maintains its own lock map.
The database pessimistic write lock still guarantees that no failure count update is lost.
A database-only locking approach would also require inserting a login failure row on the first login of every user (before knowing if it is a success or failure) in order to have a row to lock. This would significantly increase the number of rows and the IOPS on the login failure table.
This improves on the base DefaultBruteForceProtector, which processes failures asynchronously
and would reject concurrent login attempts entirely during that window.
-
Field Summary
Fields inherited from class org.keycloak.services.managers.DefaultBruteForceProtector
ALLOWED_AUTHENTICATION_CATEGORIES, factory, maxDeltaTimeSeconds, OTP_CATEGORYFields inherited from interface org.keycloak.services.managers.BruteForceProtector
DISABLED_BY_PERMANENT_LOCKOUT -
Constructor Summary
ConstructorsConstructorDescriptionDefaultLockingBruteForceProtector(KeycloakSessionFactory factory, KeycloakSession session, ConcurrentMap<String, org.keycloak.services.managers.DefaultLockingBruteForceProtector.UserLock> userLocks) -
Method Summary
Modifier and TypeMethodDescriptionprotected UserLoginFailureModelgetUserFailureModel(KeycloakSession session, RealmModel realm, String userId) protected voidprocessLogin(RealmModel realm, UserModel user, ClientConnection clientConnection, jakarta.ws.rs.core.UriInfo uriInfo, boolean success, Set<String> categories) Methods inherited from class org.keycloak.services.managers.DefaultBruteForceProtector
cleanUpPermanentLockout, close, failedLogin, failure, isPermanentlyLockedOut, isTemporarilyDisabled, sendEvent, shutdown, success, successfulLogin
-
Constructor Details
-
DefaultLockingBruteForceProtector
public DefaultLockingBruteForceProtector(KeycloakSessionFactory factory, KeycloakSession session, ConcurrentMap<String, org.keycloak.services.managers.DefaultLockingBruteForceProtector.UserLock> userLocks)
-
-
Method Details
-
processLogin
protected void processLogin(RealmModel realm, UserModel user, ClientConnection clientConnection, jakarta.ws.rs.core.UriInfo uriInfo, boolean success, Set<String> categories) - Overrides:
processLoginin classDefaultBruteForceProtector
-
getUserFailureModel
protected UserLoginFailureModel getUserFailureModel(KeycloakSession session, RealmModel realm, String userId) - Overrides:
getUserFailureModelin classDefaultBruteForceProtector
-