scroll

Lock Account after “x” amount of attempts — Spring Security

BY BENJ POWER

Introduction

A simple solution to a simple problem. I expected a concise answer to already be out there but I could not find it; so I wrote it. If you are interested in the details surrounding this solution, read on after the code snippets.

Config class extending WebSecurityConfigurerAdapter:

PreAuthPasswordChecker

PostAuthPasswordChecker

Locking a user’s account after a certain amount of incorrect password attempts is a common requirement for an application. To my surprise —because Spring is what I consider to be magic — Spring Security does not possess out-of-the-box functionality to achieve this. There is, however (of course), a mechanism for developers to implement such behavior.

Oddly my Google searches did not turn up any surefire solutions. The answers I found were all spurious; either being outdated or behaving incorrectly.


The first attempt


Worked perfectly for the error scenario. The success scenario was where we realized that an ApplicationListener type solution was not viable.

The Event above would not fire at all, and AuthenticationSuccessEvent would fire even when the password was incorrect. So, AuthenticationFailureBadCredentialsEvent would fire, followed by AuthenticationSuccessEvent which is obviously incorrect. Perhaps I do not understand how these events truly behave but regardless, their respective names are rather misleading.

Getting back to the decided upon solution:

setPostAuthenticationChecks: runs only after a user has supplied valid credentials.

setPreAuthenticationChecks: runs before the supplied credentials are validated. NOTE: this checker is executed after loadUserByUsername which means that the username value present on the UserDetails argument will be valid — assuming you throw an exception in loadUserByUsername if the username is incorrect —so you just need to validate the password at this point.

The two methods above are the fundamental strategies required. The password logic you implement in the overridden check does not have to be identical to ours. For those of you interested in our implementation:

Pre Authentication

  • Inject UserRepository into the Checker
  • call findByUsername. NOTE: we throw an exception in loadUserByUsername, so at this point, the validity of the username is incontrovertible
  • If the number of attempts ≥ 3, set status to AccountStatus.LOCKED and throw an AccountLockedException. If not, increment the passwordCounter, irrespective of whether the password is valid. We increment regardless of password validity because if the User is authenticated (the password is valid), the post authentication checker will run and reset the counter.
  • Create an ExceptionHandler for AccountLockedException so that a suitable error response may be returned to the client

  • Post Authentication

  • At this point we know that the supplied credentials are valid
  • Inject UserRepository into the Checker
  • Call findByUsername
  • Set the passwordCounter field on UserDetails to 0, set status field to UserStatus.ACTIVE, and update the entry
  • Please feel free to ask any questions. I am always open to suggestions pertaining to the improvement of my writing

    Thanks for reading