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

  1. Fix the underlying status: unlock the account in the user store or correct isAccountNonLocked() in your UserDetails
  2. If the interface method was not overridden, implement it — all UserDetails default status methods return false
  3. Catch LockedException around check()/authentication and present an account-locked UX flow
  4. 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

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


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/ce38e03c7d3045b1. Report an issue: GitHub.