spring-projects/spring-security · error · LockedException
AccountStatusUserDetailsChecker.locked
AccountStatusUserDetailsChecker.locked
Error message
User account is locked
What it means
AccountStatusUserDetailsChecker.check validates a UserDetails' account status flags in order (locked, disabled, expired, credentials expired) and throws the corresponding AuthenticationException for the first failed flag. With message code AccountStatusUserDetailsChecker.locked it throws LockedException 'User account is locked' when isAccountNonLocked() is false.
Source
Thrown at core/src/main/java/org/springframework/security/authentication/AccountStatusUserDetailsChecker.java:46
import org.springframework.util.Assert;
/**
* A {@link UserDetailsChecker} that verifies the account status flags on a
* {@link UserDetails}.
*
* @author Luke Taylor
*/
public class AccountStatusUserDetailsChecker implements UserDetailsChecker, MessageSourceAware {
private final Log logger = LogFactory.getLog(getClass());
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
@Override
public void check(UserDetails user) {
if (!user.isAccountNonLocked()) {
this.logger.debug("Failed to authenticate since user account is locked");
throw new LockedException(
this.messages.getMessage("AccountStatusUserDetailsChecker.locked", "User account is locked"));
}
if (!user.isEnabled()) {
this.logger.debug("Failed to authenticate since user account is disabled");
throw new DisabledException(
this.messages.getMessage("AccountStatusUserDetailsChecker.disabled", "User is disabled"));
}
if (!user.isAccountNonExpired()) {
this.logger.debug("Failed to authenticate since user account is expired");
throw new AccountExpiredException(
this.messages.getMessage("AccountStatusUserDetailsChecker.expired", "User account has expired"));
}
if (!user.isCredentialsNonExpired()) {
this.logger.debug("Failed to authenticate since user account credentials have expired");
throw new CredentialsExpiredException(this.messages
.getMessage("AccountStatusUserDetailsChecker.credentialsExpired", "User credentials have expired"));
}
}View on GitHub (pinned to 96852e8860)
Solutions
- Fix the underlying status: unlock the account in the user store or correct isAccountNonLocked() in your UserDetails
- If the interface method was not overridden, implement it — all UserDetails default status methods return false
- Catch LockedException around check()/authentication and present an account-locked UX flow
- Audit all four flags (locked, enabled, accountNonExpired, credentialsNonExpired) since check() reports only the first failure
Example fix
// before
userDetailsChecker.check(user); // throws LockedException
// after
try { userDetailsChecker.check(user); }
catch (LockedException e) { auditLog.record("locked", user.getUsername()); throw e; } Defensive patterns
Strategy: try-catch
Validate before calling
if (!user.isAccountNonLocked()) throw new LockedException("locked: " + user.getUsername()); Type guard
boolean statusOk(UserDetails u) { return u.isAccountNonLocked() && u.isEnabled() && u.isAccountNonExpired() && u.isCredentialsNonExpired(); } Try / catch
try { checker.check(user); }
catch (LockedException | DisabledException | AccountExpiredException | CredentialsExpiredException e) {
throw new BadCredentialsException("account-status", e);
} Prevention
- Validate all four status flags before calling check(); it stops at the first failure
- Implement every UserDetails method — defaults are false
- Keep an unlock mechanism (admin endpoint/scheduled job)
- Log which flag failed for support diagnostics
When it happens
Trigger: Calling check(user) (e.g. from a UserDetailsService-based login flow or DaoAuthenticationProvider's pre-auth checks) with a UserDetails whose isAccountNonLocked() returns false.
Common situations: Lockout policies locking accounts after failed attempts; database flag account_non_locked=0; custom UserDetails not overriding isAccountNonLocked(); running check() manually in custom authentication code.
Related errors
- AbstractUserDetailsAuthenticationProvider.locked
- AbstractUserDetailsAuthenticationProvider.disabled
- AbstractUserDetailsAuthenticationProvider.expired
- User account is locked
- ${ex.getStatus().getDefaultMessage()}
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/ce38e03c7d3045b1.
Report an issue: GitHub.