Brute force, lockout and auth events

Spring Security · lesson 22 of 31 · 4 min read

Count failed logins with Spring Security events and lock an account before an attacker guesses it.

Open this lesson in the learning hub

Key points

  • Spring Security publishes an AuthenticationSuccessEvent and an AbstractAuthenticationFailureEvent per attempt.
  • Listen with @EventListener and count failures per username and per source IP - either one alone is easy to dodge.
  • The lock lives on your user row: isAccountNonLocked() returning false makes the provider throw LockedException.
  • Locked and disabled are checked before the password, so a locked account fails even once the attacker guesses right.
  • Prefer a timed lock, say 15 minutes. A permanent one hands an attacker a denial-of-service switch for any known email.
  • Never reveal which half was wrong. One "bad credentials" message for both stops attackers enumerating your users.

Example

@Component
public class LoginAttemptListener {

    private final Map<String, Integer> failures = new ConcurrentHashMap<>();
    private final UserRepository users;

    @EventListener
    public void onFailure(AbstractAuthenticationFailureEvent event) {
        String name = String.valueOf(event.getAuthentication().getName());
        int n = failures.merge(name, 1, Integer::sum);
        if (n >= 5) {
            users.lockUntil(name, Instant.now().plus(15, ChronoUnit.MINUTES));
        }
    }

    @EventListener
    public void onSuccess(AuthenticationSuccessEvent event) {
        failures.remove(event.getAuthentication().getName());
    }
}

Count failures from events, lock for a while, and leak nothing about why.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Spring Security course, and every lesson in it is listed on the Spring Security contents page.