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
- Extend the account's expiry date or set isAccountNonExpired() to return the correct value
- Implement an account-renewal process for time-limited accounts
- If account expiry is unused, always return true from isAccountNonExpired()
- 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
- Use nullable expiry dates when expiry is optional
- Notify users before expiry
- Cover all UserDetails status flags in unit tests
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
- AccountStatusUserDetailsChecker.expired
- RunAsImplAuthenticationProvider.incorrectKey
- Authenticated principal required to operate with ACLs
- CasAuthenticationProvider.incorrectKey
- oidc_provider_not_configured
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/c6507e9cc53cbbaa.
Report an issue: GitHub.