spring-projects/spring-security · error · LockedException

AbstractUserDetailsAuthenticationProvider.locked

AbstractUserDetailsAuthenticationProvider.locked

Error message

User account is locked

What it means

AbstractUserDetailsReactiveAuthenticationManager.defaultPreAuthenticationChecks runs before password verification in reactive authentication. If UserDetails.isAccountNonLocked() returns false, it throws LockedException with message 'User account is locked' (message code AbstractUserDetailsAuthenticationProvider.locked). Authentication is aborted regardless of credentials.

Source

Thrown at core/src/main/java/org/springframework/security/authentication/AbstractUserDetailsReactiveAuthenticationManager.java:77

	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

	private PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();

	private ReactiveUserDetailsPasswordService userDetailsPasswordService = ReactiveUserDetailsPasswordService.NOOP;

	private Scheduler scheduler = Schedulers.boundedElastic();

	private UserDetailsChecker preAuthenticationChecks = this::defaultPreAuthenticationChecks;

	private UserDetailsChecker postAuthenticationChecks = this::defaultPostAuthenticationChecks;

	private @Nullable ReactiveCompromisedPasswordChecker compromisedPasswordChecker;

	private void defaultPreAuthenticationChecks(UserDetails user) {
		if (!user.isAccountNonLocked()) {
			this.logger.debug("User account is locked");
			throw new LockedException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked",
					"User account is locked"));
		}
		if (!user.isEnabled()) {
			this.logger.debug("User account is disabled");
			throw new DisabledException(
					this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
		}
		if (!user.isAccountNonExpired()) {
			this.logger.debug("User account is expired");
			throw new AccountExpiredException(this.messages
				.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
		}
	}

	private void defaultPostAuthenticationChecks(UserDetails user) {
		if (!user.isCredentialsNonExpired()) {
			this.logger.debug("User account credentials have expired");
			throw new CredentialsExpiredException(this.messages.getMessage(

View on GitHub (pinned to 96852e8860)

Solutions

  1. Return true from isAccountNonLocked() in your UserDetails implementation (or fix the account_non_locked flag in the user store)
  2. Unlock the account in your user database / admin tooling
  3. Catch LockedException in the reactive pipeline and surface a clear account-locked message to the client
  4. If you want to skip these checks, supply a custom UserDetailsChecker via setPreAuthenticationChecks that omits the lock check

Example fix

// before
@Override
public boolean isAccountNonLocked() { return locked; } // locked flag stuck false
// after
@Override
public boolean isAccountNonLocked() { return !accountLockedByAdmin(); }
Defensive patterns

Strategy: try-catch

Validate before calling

if (!userDetails.isAccountNonLocked()) throw new LockedException("Account locked: " + username);

Type guard

boolean authenticatable(UserDetails u) { return u.isAccountNonLocked() && u.isEnabled() && u.isAccountNonExpired(); }

Try / catch

authManager.authenticate(token)
  .onErrorResume(LockedException.class, e -> Mono.error(new AuthFailure("ACCOUNT_LOCKED")));

Prevention

When it happens

Trigger: Authenticating (authenticate(Mono<Authentication>)) a UserDetails whose isAccountNonLocked() returns false — typically a custom UserDetails implementation, or a JdbcUserDetailsManager users table row with enabled/non_locked flag set to locked.

Common situations: Accounts locked by brute-force lockout policies; users table schema where account_non_locked column defaults to 0/false; custom UserDetails beans forgetting to override isAccountNonLocked() (default false on UserDetails itself).

Related errors


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