spring-projects/spring-security · error · AccountExpiredException

AbstractUserDetailsAuthenticationProvider.expired

AbstractUserDetailsAuthenticationProvider.expired

Error message

User account has expired

What it means

defaultPreAuthenticationChecks throws AccountExpiredException with 'User account has expired' (message code AbstractUserDetailsAuthenticationProvider.expired) when UserDetails.isAccountNonExpired() returns false. This is a pre-authentication check in the reactive authentication manager; the account's validity period has ended.

Source

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

	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(
					"AbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired"));
		}
	}

	@Override
	public Mono<Authentication> authenticate(Authentication authentication) {
		String username = authentication.getName();
		String presentedPassword = (authentication.getCredentials() != null)
				? authentication.getCredentials().toString() : null;
		// @formatter:off

View on GitHub (pinned to 96852e8860)

Solutions

  1. Return true from isAccountNonExpired() (or extend the expiry date in the user store) for accounts that should remain valid
  2. Implement a renewal process updating the account expiry timestamp
  3. Catch AccountExpiredException and prompt the user to renew/reactivate
  4. Check your UserDetails overrides all four status methods; interface defaults are all false

Example fix

// before
@Override public boolean isAccountNonExpired() { return false; }
// after
@Override public boolean isAccountNonExpired() { return expiryDate.isAfter(Instant.now()); }
Defensive patterns

Strategy: try-catch

Validate before calling

if (!userDetails.isAccountNonExpired()) throw new AccountExpiredException("Account expired: " + username);

Type guard

boolean accountCurrent(UserDetails u) { return u.isAccountNonExpired(); }

Try / catch

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

Prevention

When it happens

Trigger: authenticate() on a UserDetails whose isAccountNonExpired() returns false — typically accounts with an expiry date in the past (e.g. temporary/contract accounts) or custom UserDetails not overriding isAccountNonExpired() (default false).

Common situations: Time-limited accounts (contractors, trials) passing their expiry date; custom User implementations omitting isAccountNonExpired(); test fixtures with hardcoded expiry timestamps now in the past.

Related errors


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