spring-projects/spring-security · error · AccountExpiredException
User account has expired
Error message
User account has expired
What it means
ActiveDirectoryLdapAuthenticationProvider maps AD bind-failure sub-codes to Spring Security exceptions. When AD bind fails with sub-error 701 (ACCOUNT_EXPIRED, account validity elapsed), raiseExceptionForErrorCode throws AccountExpiredException('User account has expired') wrapping the ActiveDirectoryAuthenticationException as cause.
Source
Thrown at ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java:269
if (message == null) {
return -1;
}
Matcher matcher = SUB_ERROR_CODE.matcher(message);
if (matcher.matches()) {
return Integer.parseInt(matcher.group(1), 16);
}
return -1;
}
private void raiseExceptionForErrorCode(int code, NamingException exception) {
String hexString = Integer.toHexString(code);
Throwable cause = new ActiveDirectoryAuthenticationException(hexString, exception.getMessage(), exception);
switch (code) {
case PASSWORD_EXPIRED -> throw new CredentialsExpiredException(this.messages
.getMessage("LdapAuthenticationProvider.credentialsExpired", "User credentials have expired"), cause);
case ACCOUNT_DISABLED -> throw new DisabledException(
this.messages.getMessage("LdapAuthenticationProvider.disabled", "User is disabled"), cause);
case ACCOUNT_EXPIRED -> throw new AccountExpiredException(
this.messages.getMessage("LdapAuthenticationProvider.expired", "User account has expired"), cause);
case ACCOUNT_LOCKED -> throw new LockedException(
this.messages.getMessage("LdapAuthenticationProvider.locked", "User account is locked"), cause);
default -> throw badCredentials(cause);
}
}
private String subCodeToLogMessage(int code) {
return switch (code) {
case USERNAME_NOT_FOUND -> "User was not found in directory";
case INVALID_PASSWORD -> "Supplied password was invalid";
case NOT_PERMITTED -> "User not permitted to logon at this time";
case PASSWORD_EXPIRED -> "Password has expired";
case ACCOUNT_DISABLED -> "Account is disabled";
case ACCOUNT_EXPIRED -> "Account expired";
case PASSWORD_NEEDS_RESET -> "User must reset password";
case ACCOUNT_LOCKED -> "Account locked";
default -> "Unknown (error code " + Integer.toHexString(code) + ")";View on GitHub (pinned to 96852e8860)
Solutions
- Ask an AD administrator to extend or clear the account expiration date (Set-ADAccountExpiration or set accountExpires to never).
- Confirm the correct account is being used.
- Catch AccountExpiredException in your auth failure handler to surface an account-expired message.
- For service accounts, set the account to never expire and control access via groups instead.
Example fix
// before
catch (BadCredentialsException e) { return "login?error"; }
// after
catch (AccountExpiredException e) { return "redirect:/account/expired"; } Defensive patterns
Strategy: try-catch
Try / catch
try {
return authenticationManager.authenticate(token);
} catch (AccountExpiredException e) {
return "redirect:/account/expired";
} Prevention
- Audit accounts with accountExpires set and renew before expiry.
- For service accounts, prefer never-expiring accounts controlled by group membership.
- Catch AccountExpiredException distinctly to guide user to renewal.
- Log ActiveDirectoryAuthenticationException sub-code (data 701) for audit.
When it happens
Trigger: authenticate() -> handleBindException() -> raiseExceptionForErrorCode(ACCOUNT_EXPIRED), raised when the LDAP bind against AD fails with sub-error code 701 indicating the account's accountExpires date has passed.
Common situations: Temporary/contractor accounts with an accountExpires date that elapsed; accounts created with an expiration window never extended; seasonal accounts auto-expiring per policy.
Related errors
- User credentials have expired
- User is disabled
- User account is locked
- managerPassword is required if managerDn is supplied
- No BaseLdapPathContextSource instances found. Have you added
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/4b96d85259046407.
Report an issue: GitHub.