spring-projects/spring-security · error · AccountExpiredException

User account has expired

Error message

User account has expired

What it means

DefaultPreAuthenticationChecks.check() in AbstractUserDetailsAuthenticationProvider throws AccountExpiredException 'User account has expired' when UserDetails.isAccountNonExpired() returns false. The credentials may be correct but the account's validity window has ended.

Source

Thrown at core/src/main/java/org/springframework/security/authentication/dao/AbstractUserDetailsAuthenticationProvider.java:385

		@Override
		public void check(UserDetails user) {
			if (!user.isAccountNonLocked()) {
				AbstractUserDetailsAuthenticationProvider.this.logger
					.debug("Failed to authenticate since user account is locked");
				throw new LockedException(AbstractUserDetailsAuthenticationProvider.this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
			}
			if (!user.isEnabled()) {
				AbstractUserDetailsAuthenticationProvider.this.logger
					.debug("Failed to authenticate since user account is disabled");
				throw new DisabledException(AbstractUserDetailsAuthenticationProvider.this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
			}
			if (!user.isAccountNonExpired()) {
				AbstractUserDetailsAuthenticationProvider.this.logger
					.debug("Failed to authenticate since user account has expired");
				throw new AccountExpiredException(AbstractUserDetailsAuthenticationProvider.this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
			}
		}

	}

	private class DefaultPostAuthenticationChecks implements UserDetailsChecker {

		@Override
		public void check(UserDetails user) {
			if (!user.isCredentialsNonExpired()) {
				AbstractUserDetailsAuthenticationProvider.this.logger
					.debug("Failed to authenticate since user account credentials have expired");
				throw new CredentialsExpiredException(AbstractUserDetailsAuthenticationProvider.this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.credentialsExpired",
							"User credentials have expired"));
			}
		}

View on GitHub (pinned to 96852e8860)

Solutions

  1. Extend the account's expiry date or set isAccountNonExpired() to return the correct value
  2. Implement an account-renewal process for time-limited accounts
  3. If account expiry is unused, always return true from isAccountNonExpired()
  4. Catch AccountExpiredException in the failure handler to show an account-expired message

Example fix

// before
@Override
public boolean isAccountNonExpired() { return false; }
// after
@Override
public boolean isAccountNonExpired() { return this.expiryDate == null || Instant.now().isBefore(this.expiryDate); }
Defensive patterns

Strategy: try-catch

Validate before calling

UserDetails user = uds.loadUserByUsername(username);
if (!user.isAccountNonExpired()) { throw new IllegalStateException("Account expired: " + username); }

Type guard

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

Try / catch

try { authMgr.authenticate(token); } catch (AccountExpiredException e) { return ResponseEntity.status(403).body("Account has expired."); }

Prevention

When it happens

Trigger: DaoAuthenticationProvider pre-authentication check on a UserDetails with isAccountNonExpired() == false.

Common situations: Contract or trial accounts past their end date; expiry computed from a DB date column that has passed; custom UserDetails returning false inadvertently.

Related errors


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